agda / agda

Agda is a dependently typed programming language / interactive theorem prover.

Home Page:https://wiki.portal.chalmers.se/agda/pmwiki.php

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Prune the `Makefile`

lawcho opened this issue · comments

This ticket continues the discussion spread across #7115 #7117, and less recently #6262, #6031

Problems with Agda's Makefile

Proposal

  • Find out:

    • What commands developers currently use to build Agda
    • What kind of Agda builds developers want (in addition to the list above)
    • e.g. via a Zulip poll
    • e.g. via a GitHub issue
  • Prune the Makefile:

    1. Delete user-facing installation targets
    2. Delete under-used developer-facing targets (as determined above)
    3. Move remaining targets into either...
      • The .cabal file (most re-usable)
      • A cabal.project file
      • A directory of bash build scripts (most flexible)
    4. Delete the (now empty) Makefile
  • Explain in HACKING.md:

    • To look at the installation docs first to see the "easy way" of installing Agda
    • How to build Agda (each of the build types listed above)
  • Update the dev section of the installation docs:

    • To not duplicate information from HACKING.md
    • To instead point to HACKING.md

This is a great initiative. As a developer, I mostly use make type-check (though increasingly replacing this with HLS), make quicker-install-bin (for quick iterations), and make install-bin (for running the test suite), plus for testing make test, make quicktest, make succeed, make fail, make interaction, make std-lib-test, make cubical-test, etc (depending on which part of Agda I'm working on). I also occasionally need to pass in an argument for getting a binary with a custom suffix, and I always have to look up how exactly that works. Other that that there is mostly a lot of stuff that I don't understand and don't use.

I have a default.nix that also pulls in haskell-language-server, which I prefer over make typecheck (though on memory constrained systems I would probably use ghcid instead). I switch between

$ cabal v2-build --ghc-options=-O0 --builddir=dist-newstyle -fdebug
$ cabal v2-build --ghc-options="-O2 -j" -foptimise-heavily --builddir=dist-optimise-heavily -fdebug -- agda
# ^^ these ghc-options are probably placebo but they're in my shell history

to iterate. Code is set up to use the agda in dist-newstyle so I can iterate on interactive testing and I have

BASE_DIR ?= /home/amelia/default/Projects/agda/dist-optimise-heavily/build/x86_64-linux/ghc-9.6.3/Agda-2.6.5/build/
AGDA_BIN := $(BASE_DIR)/agda/agda
AGDA_TESTS_BIN := $(BASE_DIR)/agda-tests/agda-tests

as my mk/config.mk — so that make succeed, make fail, make interaction use the optimised build. I also have a pair of shell scripts for making the next TestN.agda in the root directory and for bumping the interface file version that are... extremely shell script, but I'm happy to share if anyone would find them useful 🙂

I use cabal repl instead of make type-check. The make targets I use are install-bin-debug, debug-install-quick, and the various different test targets (succeed, fail, interaction, std-lib-test, etc).

I don't understand how you imagine non-phony targets would work. Binaries are put in weird system specific locations, and the rules for recompilation are non-trivial. Even if you manage it, it will complicate the Makefile a lot, and the only benefit is that the Makefile is no longer "unusual"?

RE local builds & non-phony goals (@UlfNorell, @andreasabel)

the only benefit is that the Makefile is no longer "unusual"?

Another benefit is exploiting make's timestamp-based early-cut-off.

I don't understand how you imagine non-phony targets would work.

  • Write agda binaries to build/ (rather than ~/.local/bin/)
  • Name agda binaries descriptively (rather than always agda), e.g.
    • agda-quicker
    • agda-debug-quicker
    • agda-optimized
  • Write non-phony build/agda-* targets to generate the binaries
  • Write phony *-build aliases for the non-phony build/agda-* targets, e.g.
    build: build/agda-optimized
    quickbuild: build/agda-quicker
    debugbuild: build/agda-debug-quicker
  • Rewrite *-test targets to depend on appropriate binaries, e.g.
    test: build/agda-optimized
      	...
    quicktest: build/agda-quicker
      	...
    std-lib-test: build/agda-optimized
      	...
  • Rewrite phony *-install targets as thin wrappers, e.g.
    install: build/agda-optimized
    	cp $< $(SYSTEM_INSTALL_DIR)/agda
    quickinstall: build/agda-quicker
    	cp $< $(SYSTEM_INSTALL_DIR)/agda
  • Use env vars (e.g. DEBUG=1) for build variants that shouldn't trigger a re-build on test

However, this assumes we keep the Makefile at all.

Dropping the Makefile and using cabal v2-build directly also provides early-cut-off.

You left out how to tell make about the dependencies. For instance,

  • All the Haskell sources
  • The primitive libraries
  • The emacs mode
  • I did cabal update and want to rebuild dependencies
  • I switched ghc version
  • Probably a bunch more that cabal keeps track of that's we'd have to encode manually

Another benefit is exploiting make's timestamp-based early-cut-off.

That only works if you can accurately encode the dependencies. At the moment cabal handles all that for us.

Rewrite phony *-install targets as thin wrappers

What about installing the primitive libraries and the emacs mode? Are you planning to write "thin" wrappers for those to?

However, this assumes we keep the Makefile at all.

  • What's your vision for the test suites? What would I write to run only the stdlib tests (for instance)? What does that setup look like?
  • What about other targets, like make up-to-date-stdlib or make workflows?
  • How do you work around the problem with v2-build followed by v2-install rebuilding everything from scratch?

You left out how to tell make about the dependencies.

build/agda-optimized: $(shell find src/full/) would cover some of the dependencies.

At the moment cabal handles all that for us.

Yes. I'd prefer to drop the Makefile entirely and use cabal v2-* directly.

What's your vision for the test suites? What would I write to run only the stdlib tests (for instance)? What does that setup look like?

  • To run all the tests: cabal test

  • To run std-lib tests: cabal test std-lib

  • To run succeed tests: cabal test succeed

  • To skip tests for Opaque: cabal test --test-option="--regex-excude=all/Succeed/Opaque"

  • Named sub-test-suites to be ported from Makefile to Agda.cabal

  • Docs for cabal test:

    ~$ cabal test --help | head
    Run test-suites.
    
    Usage: cabal test [TARGETS] [FLAGS]
    
    Runs the specified test-suites, first ensuring they are up to date.
    
    Any test-suite in any package in the project can be specified. A package can
    be specified in which case all the test-suites in the package are run. The
    default is to run all the test-suites in the package in the current directory.

make up-to-date-stdlib

First, run git submodule update --init std-lib
(my IDE's git integration reminds me to do this anyway).

Then manually call make -C std-lib setup

Or (cd std-lib; cabal run GenerateEverything -- --out-dir doc),
since that is what the stdlib's Makefile ends up running.

Or don't run GenerateEverything at all.
It appears to only generate documentation and is not mentioned in the stdlib install instructions,
so the test suite should work without it.

make workflows?

make -C .github/workflows

How do you work around the problem with v2-build followed by v2-install rebuilding everything from scratch?

Don't call v2-install. Instead cabal exec install.
Where the install target is backed by glue currently in Setup.hs.

I generally agree with your suggestions to move the more complex logic from the Makefile to Agda.cabal, but we should be careful that the interface does not get more complex. In particular, make up-to-date-stdlib is really easy to remember in a way that some sequence of git submodule ... and cabal run ... isn't. The same goes for make workflows. Having a canonical way to make these targets without having to pass any flags is a real added value.