Lecture 8

  1. Most makefiles provide a target called clean. This isn’t intended to produce a file called clean, but instead to clean up any files that can be re-built by make. Think of it as a way to “undo” all of the build steps. Implement a clean target for the paper.pdf Makefile above. You will have to make the target phony. You may find the git ls-files subcommand useful. A number of other very common make targets are listed here.

    Makefile

    paper.pdf: paper.tex plot-data.png
    	pdflatex paper.tex
    
    plot-%.png: %.dat plot.py
    	./plot.py -i $*.dat -o $@
    
    .PHONY: clean
    clean:
    rm -f $(shell git ls-files -o --exclude-standard)
    

    .PHONY tells make that clean is a command and not a file, ensuring it will be executed everytime.

    git ls-files -o returns files that aren't tracked by git (so files created in the make process).
    --exclude-standard excludes files that are ignored by git (like in .gitignore_global).

  2. Take a look at the various ways to specify version requirements for dependencies in Rust’s build system. Most package repositories support similar syntax. For each one (caret, tilde, wildcard, comparison, and multiple), try to come up with a use-case in which that particular kind of requirement makes sense.
    • Caret (^): Allows updates that don’t break compatibility, common for libraries with semantic versioning.
      Use case: ^1.2.3 allows any 1.x.x greater than 1.2.3.
    • Tilde (~): Allows updates within a minor version.
      Use case: ~1.2.3 allows updates up to 1.3.x, keeping more control over changes.
    • Wildcard (*): Allows any version in a certain range.
      Use case: 1.* when you’re flexible about any version within 1.x.x.
    • Comparison (>=, <, etc.): Specific range constraints.
      Use case: >=1.2.3, <2.0.0 for strict boundaries.
    • Multiple requirements: Combines different rules.
      Use case: >=1.2, <2.0 || >=2.1, <3.0 for handling major version transitions.
  3. Git can act as a simple CI system all by itself. In .git/hooks inside any git repository, you will find (currently inactive) files that are run as scripts when a particular action happens. Write a pre-commit hook that runs make paper.pdf and refuses the commit if the make command fails. This should prevent any commit from having an unbuildable version of the paper.
    Add the following script to .git/hooks/pre-commit:

    pre-commit

    #!/bin/bash
    
    # Run make to build the paper
    if ! make paper.pdf; then
    	echo "Build failed. Commit aborted."
        exit 1
    fi
    
    Make sure the code is executable with chmod +x .git/hooks/pre-commit
  4. Set up a simple auto-published page using GitHub Pages. Add a GitHub Action to the repository to run shellcheck on any shell files in that repository (here is one way to do it). Check that it works!

    Create a github repo like [your-github-username].github.io.

    You can modify the page's settings in the Pages option on the side-menu under the settings section.

    Otherwise, create the .github/workflows/shellcheck.yml file and add the following lines:

    shellsheck.yml

    name: Shellcheck
    
    on: [push, pull_request]
    
    jobs:
      shellcheck:
          runs-on: ubuntu-latest
    
      steps:
          - name: Checkout repository
            uses: actions/checkout@v2
    
          - name: Run Shellcheck
            uses: ludeeus/action-shellcheck@2.0.0
            with:
    	        files: "**/*.sh"
    
    After that, commit and push the file to Github (you might need to update token permissions to allow workflow scope).
  5. Build your own GitHub action to run proselint or write-good on all the .md files in the repository. Enable it in your repository, and check that it works by filing a pull request with a typo in it.
      Step 1: Create the action
    1. Create the directory for your actions:
      mkdir -p .github/actions/proselint
    2. Create the action file in .github/actions/proselint:

      action.yml

      name: Proselint
      
      description: Run Proselint on markdown files
      
      runs:
        using: "docker"
        image: "Dockerfile"
      
    3. Create the Dockerfile in the same directory:
      This will quickly set up the right environment to execute the action.

      Dockerfile

      FROM python:3.8-slim
      
      RUN pip install proselint
      
      COPY entrypoint.sh /entrypoint.sh
      RUN chmod +x /entrypoint.sh
      
      ENTRYPOINT ["/entrypoint.sh"]
      
    4. Create the entrypoint script in the same directory:

      entrypoint.sh

      #!/bin/bash
      set -e
      
      # Run proselint on all .md files
      for file in $(find . -name '*.md'); do
          echo "Linting $file"
          proselint "$file"
      done
      
      Step 2: Create the workflow
    1. Create the proselint.yml file in the workflows directory you created in the previous question:

      entrypoint.sh

      name: Proselint Check
      
      on:
        pull_request:
          branches:
            - main
        push:
          branches:
      	  - main
      
      jobs:
        proselint:
          runs-on: ubuntu-latest
      
          steps:
          - name: Checkout repository
            uses: actions/checkout@v2
      
          - name: Run Proselint
            uses: ./.github/actions/proselint
      
    2. Commit and push your new action and workflow files.
    3. Test it by pushing an md file with errors.