hledger/justfile
2023-12-15 18:29:42 -10:00

542 lines
18 KiB
Makefile

#!/usr/bin/env just
# * Light project scripts, without file dependendencies
# using https://github.com/casey/just 0.16.
# https://docs.rs/regex/1.5.4/regex/#syntax Regexps
# https://just.systems/man/en/chapter_31.html Functions
# https://cheatography.com/linux-china/cheat-sheets/justfile Cheatsheet
# https://github.com/casey/just/discussions
# See also Makefile, Shake.hs.
# ** prelude
@help:
just -lu
@_check:
just --fmt --unstable --check
@_fmt:
just -q _check || just --fmt --unstable
# ** vars
# GHC-compiled executables require a locale (and not just C) or they
# will die on encountering non-ascii data. Set LANG to something if not already set.
# export LANG? := 'en_US.UTF-8'
# command to run during profiling (time and heap)
PROFCMD := 'stack exec --profile -- hledger balance -f examples/10000x1000x10.journal >/dev/null'
#PROFRTSFLAGS := '-p'
PROFRTSFLAGS := '-P'
# # command to run during "make coverage"
# COVCMD := 'test'
# COVCMD := '-f test-wf.csv print'
# misc. system tools
BROWSE := 'open'
# VIEWHTML := '{{ BROWSE }}'
# VIEWPDF := '{{ BROWSE }}'
# PRINT := 'lpr'
#GHC := 'ghc'
GHCI := 'ghci'
# GHCPKG := 'ghc-pkg'
# HADDOCK := 'haddock'
# CABAL := 'cabal'
# CABALINSTALL := 'cabal install -w {{ GHC }}'
# Which stack command (and in particular, stack yaml/GHC version) to use for building etc. ?
STACK := 'stack'
#STACK := 'stack --stack-yaml=stack8.10.yaml'
# Or override temporarily with an env var:
# STACK := '"stack --stack-yaml=stack8.10.yaml" make functest'
# Which stack command (stack yaml, GHC version) to use for ghci[d] operations ?
STACKGHCI := STACK
#STACKGHCI := 'stack --stack-yaml=stack9.2.yaml'
# if using an unreleased stack with a newer hpack than the one mentioned in */*.cabal,
# it will give warnings. To silence these, put the old hpack-X.Y in $PATH and uncomment:
#STACK := 'stack --with-hpack=hpack-0.20'
# --threads := '16 sometimes gives "commitAndReleaseBuffer: resource vanished (Broken pipe)" but seems harmless'
# --timeout := 'N is not much use here - can be defeated by multiple threads, unoptimised builds, '
# slow hackage index or compiler setup on first build, etc.
SHELLTESTOPTS := '--execdir --threads=64 --exclude=/_'
# make sure shelltest is a released version of shelltestrunner
# run shell tests using the executable specified in tests
# SHELLTEST := 'COLUMNS=80 PATH=~/.local/bin:/usr/bin:/bin shelltest {{ SHELLTESTOPTS }}'
# run shell tests using the stack build of hledger
#SHELLTESTSTK := 'shelltest -w `stack exec which hledger` {{ SHELLTESTOPTS }}'
SHELLTESTSTK := 'COLUMNS=80 ' + STACK + ' exec -- shelltest ' + SHELLTESTOPTS
WATCHEXEC := 'watchexec'
PACKAGES := '\
hledger-lib \
hledger \
hledger-ui \
hledger-web \
'
BINARIES := '\
hledger \
hledger-ui \
hledger-web
'
INCLUDEPATHS := '\
-ihledger-lib \
-ihledger \
-ihledger-ui \
-ihledger-web \
-ihledger-web/app \
'
MAIN := 'hledger/app/hledger-cli.hs'
# All source files in the project (plus a few strays like Setup.hs & hlint.hs).
# Used eg for building tags. Doesn't reliably catch all source files.
SOURCEFILES := '\
dev.hs \
hledger/*hs \
hledger/app/*hs \
hledger/bench/*hs \
hledger/test/*hs \
hledger/Hledger/*hs \
hledger/Hledger/*/*hs \
hledger/Hledger/*/*/*hs \
hledger-*/*hs \
hledger-*/app/*hs \
hledger-*/test/*hs \
hledger-*/Hledger/*hs \
hledger-*/Hledger/*/*hs \
hledger-*/Hledger/*/*/*hs \
hledger-lib/Text/*/*hs \
'
# hledger-*/src/*hs \
HPACKFILES := '\
hledger/*package.yaml \
hledger-*/*package.yaml \
'
CABALFILES := '\
hledger/hledger.cabal \
hledger-*/*.cabal \
'
MANUALSOURCEFILES := '\
doc/common.m4 \
*/*.m4.md \
'
MANUALGENFILES := '\
hledger*/hledger*.{1,5,info,txt} \
'
COMMANDHELPFILES := '\
hledger/Hledger/Cli/Commands/*.md \
'
WEBTEMPLATEFILES := '\
hledger-web/templates/* \
'
WEBCODEFILES := '\
hledger-web/static/*.js \
hledger-web/static/*.css \
'
DOCSOURCEFILES := '\
README.md \
CONTRIBUTING.md' + MANUALSOURCEFILES + COMMANDHELPFILES
TESTFILES := '\
hledger/test/*.test \
hledger/test/*/*.test \
hledger/test/*/*/*.test \
'
# # file(s) which require recompilation for a build to have an up-to-date version string
# VERSIONSOURCEFILE := 'hledger/Hledger/Cli/Version.hs'
# Two or three-part version string, set as program version in builds made by this makefile.
# We use hledger CLI's current version (XXX for all packages, which isn't quite right).
export VERSION := `cat hledger/.version`
# Flags for ghc builds.
# Warnings to see during dev tasks like make ghci*. See also the warnings in package.yamls.
# XXX redundant with package.yamls ?
WARNINGS := '\
-Wall \
-Wno-incomplete-uni-patterns \
-Wno-missing-signatures \
-Wno-orphans \
-Wno-type-defaults \
-Wno-unused-do-bind \
'
# if you have need to try building in less memory
GHCLOWMEMFLAGS := ''
# ghc-only builds need the macro definitions generated by cabal
# from cabal's dist or dist-sandbox dir, hopefully there's just one:
#CABALMACROSFLAGS := '-optP-include -optP hledger/dist*/build/autogen/cabal_macros.h'
# or from stack's dist dir:
#CABALMACROSFLAGS := '-optP-include -optP hledger/.stack-work/dist/*/*/build/autogen/cabal_macros.h'
CABALMACROSFLAGS := ''
BUILDFLAGS := '-rtsopts ' + WARNINGS + GHCLOWMEMFLAGS + CABALMACROSFLAGS + ' -DDEVELOPMENT' + ' -DVERSION=\"' + VERSION + '\"' + INCLUDEPATHS
# -fplugin Debug.Breakpoint \
# -fhide-source-paths \
# PROFBUILDFLAGS := '-prof -fprof-auto -osuf hs_p'
TIME := "{{ shell date +'%Y%m%d%H%M' }}"
MONTHYEAR := "{{ shell date +'%B %Y' }}"
# ** GHCI
GHCI:
# run GHCI on hledger-lib + hledger
@ghci:
{{ STACKGHCI }} exec -- {{ GHCI }} {{ BUILDFLAGS }} hledger/Hledger/Cli.hs
# run GHCI on hledger-lib + hledger with profiling/call stack information
@ghci-prof:
stack build --profile hledger --only-dependencies
{{ STACKGHCI }} exec -- {{ GHCI }} {{ BUILDFLAGS }} -fexternal-interpreter -prof -fprof-auto hledger/Hledger/Cli.hs
# # run GHCI on hledger-lib + hledger + dev.hs script
# @ghci-dev:
# {{ STACKGHCI }} exec -- {{ GHCI }} {{ BUILDFLAGS }} -fno-warn-unused-imports -fno-warn-unused-binds dev.hs
# run GHCI on hledger-lib + hledger + hledger-ui
@ghci-ui:
{{ STACKGHCI }} exec -- {{ GHCI }} {{ BUILDFLAGS }} hledger-ui/Hledger/UI/Main.hs
# run GHCI on hledger-lib + hledger + hledger-web
@ghci-web:
{{ STACKGHCI }} exec -- {{ GHCI }} {{ BUILDFLAGS }} hledger-web/app/main.hs
# run GHCI on hledger-lib + hledger + hledger-web + hledger-web test suite
@ghci-web-test:
{{ STACKGHCI }} exec -- {{ GHCI }} {{ BUILDFLAGS }} hledger-web/test/test.hs
# # better than stack exec ?
# # XXX does not see changes to files
# # run GHCI on hledger-lib + test runner
# ghci-lib-test:
# {{ STACKGHCI }} ghci --ghc-options="\'-rtsopts {{ WARNINGS }} -ihledger-lib -DDEVELOPMENT -DVERSION=\"1.26.99\"\'" hledger-lib/test/unittest.hs
# run GHCI on all the hledger
# ghci-all:
# {{ STACK }} exec -- {{ GHCI }} {{ BUILDFLAGS }} \
# hledger-ui/Hledger/UI/Main.hs \
# hledger-web/app/main.hs \
# run GHCI on hledger-lib doctests
@ghci-doctest:
cd hledger-lib; {{ STACKGHCI }} ghci hledger-lib:test:doctest
# run GHCI on Shake.hs
@ghci-shake:
{{ STACK }} exec {{ SHAKEDEPS }} -- ghci Shake.hs
# ** ghcid
GHCID:
# run ghcid on hledger-lib + hledger
@ghcid:
ghcid -c 'just ghci'
# run ghcid autobuilder on hledger-lib + hledger + hledger-ui
@ghcid-ui:
ghcid -c 'just ghci-ui'
# run ghcid autobuilder on hledger-lib + hledger + hledger-web
@ghcid-web:
ghcid -c 'just ghci-web'
# run ghcid autobuilding and running hledger-web with sample journal on port 5001
@ghcid-web-run:
ghcid -c 'just ghci-web' --test ':main -f examples/sample.journal --port 5001 --serve'
# # run ghcid autobuilding and running the test command
# ghcid-test:
# ghcid -c 'just ghci' --test ':main test -- --color=always'
# # run ghcid autobuilding and running the test command with this TESTPATTERN
# ghcid-test-%:
# ghcid -c 'just ghci' --test ':main test -- --color=always -p$*'
# run ghcid autobuilding and running hledger-lib doctests
@ghcid-doctest:
ghcid -c 'cd hledger-lib; {{ STACK }} ghci hledger-lib:test:doctest' --test ':main' --reload hledger-lib
GHCIDRESTART := '--restart Makefile --restart Makefile.local'
GHCIDRELOAD := '--reload t.j --reload t.timedot'
GHCIDCMD := ':main -f t.j bal date:today -S'
# # run ghcid autobuilding and running a custom GHCI command with reload/restart on certain files - customise this
# ghcid-watch watch:
# ghcid -c 'just ghci' --test '{{ GHCIDCMD }}' {{ GHCIDRELOAD }} {{ GHCIDRESTART }}
# keep synced with Shake.hs header
SHAKEDEPS := '\
--package base-prelude \
--package directory \
--package extra \
--package process \
--package regex \
--package safe \
--package shake \
--package time \
'
# --package hledger-lib \ # for Hledger.Utils.Debug
# run ghcid autobuilder on Shake.hs
ghcid-shake:
stack exec {{ SHAKEDEPS }} -- ghcid Shake.hs
# ** dev.hs script
# # hledger-lib/Hledger/Read/TimeclockReaderPP.hs
# # build the dev.hs script for quick experiments (with ghc)
# dev:
# {{ STACK }} ghc -- {{ CABALMACROSFLAGS }} -ihledger-lib dev.hs \
# # to get profiling deps installed, first do something like:
# # stack build --library-profiling hledger-lib timeit criterion
# # build the dev.hs script with profiling support
# devprof:
# {{ STACK }} ghc -- {{ CABALMACROSFLAGS }} -ihledger-lib dev.hs -rtsopts -prof -fprof-auto -osuf p_o -o devprof
# # get a time & space profile of the dev.hs script
# dev-profile:
# time ./devprof +RTS -P \
# && cp devprof.prof devprof.prof.{{ TIME }} \
# && profiterole devprof.prof
# # get heap profiles of the dev.hs script
# dev-heap:
# time ./devprof +RTS -hc -L1000 && cp devprof.hp devprof-hc.hp && hp2ps devprof-hc.hp
# time ./devprof +RTS -hr -L1000 && cp devprof.hp devprof-hr.hp && hp2ps devprof-hr.hp
# dev-heap-upload:
# curl -F "file=@devprof-hc.hp" -F "title='hledger parser'" http://heap.ezyang.com/upload
# curl -F "file=@devprof-hr.hp" -F "title='hledger parser'" http://heap.ezyang.com/upload
# ** building
BUILDING:
# build the hledger package showing GHC codegen times/allocations
@buildtimes:
time ({{ STACK }} build hledger --force-dirty --ghc-options='-fforce-recomp -ddump-timings' 2>&1 | grep -E '\bCodeGen \[.*time=')
# # build an unoptimised hledger at bin/hledger.EXT.unopt (default: git describe)
# build-unopt *EXT:
# #!/usr/bin/env bash
# ext={{ if EXT == '' { `git describe --tags` } else { EXT } }}
# exe="bin/hledger.$ext.unopt"
# {{ STACK }} --verbosity=error install --ghc-options=-O0 hledger --local-bin-path=bin
# mv bin/hledger "$exe"
# echo "$exe"
# # build hledger with profiling enabled at bin/hledgerprof
# hledgerprof:
# # {{ STACK }} --verbosity=error install --local-bin-path=bin hledger
# {{ STACK }} build --profile hledger
# # hledger-lib --ghc-options=-fprof-auto
# # @echo "to profile, use {{ STACK }} exec --profile -- hledger ..."
# # build "bin/hledgercov" for coverage reports (with ghc)
# hledgercov:
# {{ STACK }} ghc {{ MAIN }} -fhpc -o bin/hledgercov -outputdir .hledgercovobjs {{ BUILDFLAGS }}
# ** installing
INSTALLING:
# # copy the current ~/.local/bin/hledger to bin/old/hledger-VER
# @copy-as VER:
# cp ~/.local/bin/hledger bin/old/hledger-{{ VER }}; echo "bin/hledger-{{ VER }}"
# stack install, then copy the hledger executables to bin/old/hledger*-VER
@installas VER:
{{ STACK }} install --local-bin-path bin/old
for e in hledger hledger-ui hledger-web ; do cp bin/old/$e bin/old/$e-{{ VER }}; echo "bin/$e-{{ VER }}"; done
# # make must be GNU Make 4.3+
# .PHONY: shellcompletions
# # update shell completions in hledger package
# shellcompletions:
# make -C hledger/shell-completion/ clean-all all
# ** releasing
RELEASING:
# Symlink/copy important files temporarily in .relfiles/.
relfiles:
#!/usr/bin/env bash
echo "linking/copying important release files in .relfiles/ for convenient access..."
mkdir -p .relfiles
cd .relfiles
for f in \
../stack.yaml \
../Shake.hs \
../hledger-install/hledger-install.sh \
../CHANGES.md \
../hledger/CHANGES.md \
../hledger-ui/CHANGES.md \
../hledger-web/CHANGES.md \
../hledger-lib/CHANGES.md \
../doc/github-release.md \
../doc/ANNOUNCE \
../doc/ANNOUNCE.masto \
../site/src/release-notes.md \
../site/src/install.md \
; do ln -sf $f .; done
cp ../doc/RELEASING.md ./RELEASING2.md # temp copy which can be edited without disruption
# Prepare to release today, creating/switching to release branch, updating versions, manuals, changelogs etc.
relprep VER:
#!/usr/bin/env bash
[[ -z {{ VER }} ]] && usage
BRANCH=$(just _versionReleaseBranch {{ VER }})
COMMIT="-c"
echo "Switching to $BRANCH, auto-creating it if needed..."
_gitSwitchAutoCreate "$BRANCH"
echo "Bumping all version strings to {{ VER }} ..."
./Shake setversion {{ VER }} $COMMIT
echo "Updating all command help texts for embedding..."
./Shake cmdhelp $COMMIT
echo "Updating all dates in man pages..."
./Shake mandates
echo "Generating all the manuals in all formats...."
./Shake manuals $COMMIT
echo "Updating CHANGES.md files with latest commits..."
./Shake changelogs $COMMIT
# Push the current branch to github to generate release binaries.
@relbin:
# assumes the github remote is named "github"
git push -f github HEAD:binaries
# *** hledger version number helpers
# (as hidden recipes, since just doesn't support global custom functions)
# See doc/RELEASING.md > Glossary.
# First 0-2 parts of a dotted version number.
@_versionMajorPart VER:
echo {{ replace_regex(VER, '(\d+(\.\d+)?).*', '$1') }}
# Third part of a dotted version number, if any.
@_versionMinorPart VER:
echo {{ if VER =~ '\d+(\.\d+){2,}' { replace_regex(VER, '\d+\.\d+\.(\d+).*', '$1') } else { '' } }}
# Fourth part of a dotted version number, if any.
@_versionFourthPart VER:
echo {{ if VER =~ '\d+(\.\d+){3,}' { replace_regex(VER, '\d+(\.\d+){2}\.(\d+).*', '$2') } else { '' } }}
# Does this dotted version number have a .99 third part and no fourth part ?
@_versionIsDev VER:
echo {{ if VER =~ '(\d+\.){2}99$' { 'y' } else { '' } }}
# Does this dotted version number have a .99 third part and a fourth part ?
@_versionIsPreview VER:
echo {{ if VER =~ '(\d+\.){2}99\.\d+' { 'y' } else { '' } }}
# Increment a major version number to the next.
# @majorVersionIncrement MAJORVER:
# python3 -c "print({{MAJORVER}} + 0.01)"
# Appropriate release branch name for the given version number.
_versionReleaseBranch VER:
#!/usr/bin/env bash
MAJOR=$(just _versionMajorPart {{ VER }})
if [[ $(just _versionIsDev {{ VER }}) == y ]] then
echo "{{ VER }} is not a releasable version" >&2
exit 1
elif [[ $(just _versionIsPreview {{ VER }}) == y ]] then
# echo "$(just majorVersionIncrement "$MAJOR")-branch"
echo "{{ VER }} is not a releasable version" >&2
exit 1
else
echo "$MAJOR-branch"
fi
# *** git helpers
# Does the named branch exist in this git repo ?
@_gitBranchExists BRANCH:
git branch -l {{ BRANCH }} | grep -q {{ BRANCH }}
# Switch to the named git branch, creating it if it doesn't exist.
_gitSwitchAutoCreate BRANCH:
#!/usr/bin/env bash
if just _gitBranchExists {{ BRANCH }}; then
git switch {{ BRANCH }}
else
git switch -c {{ BRANCH }}
fi
# ** misc
MISC:
# push to github CI, wait for tests to pass, then push to master
@push:
tools/push
# run some tests to validate the development environment
# check-setup:
# run some tests to validate the development environment\
# )
# @echo sanity-checking developer environment:
# @({{ SHELLTEST }} checks \
# && echo $@ PASSED) || echo $@ FAILED
# sym-link some directories required by hledger-web dev builds
mkwebdirs:
echo "#ln -sf hledger-web/config # disabled, causes makeinfo warnings"
ln -sf hledger-web/messages
ln -sf hledger-web/static
ln -sf hledger-web/templates
# Show last week's activity, for TWIH
@_lastweek:
echo "hledger time last 7 days including today (this should be run on a Friday):"
tt bal hledger -DTS -b '6 days ago' --drop 2
echo
echo "By activity type, percentage:"
tt bal hledger -DTS -b '6 days ago' --pivot t -% -c 1% | tail +1
echo
echo "Time log details:"
tt print hledger -b '6 days ago' | grep -E '^[^ ]|hledger'
echo
echo "main repo:"
git log --format='%C(yellow)%cd %ad %Cred%h%Creset %s %Cgreen(%an)%Creset%C(bold blue)%d%Creset' --date=short --since="6 days ago" --reverse
echo
echo "site repo:"
git -C site log --format='%C(yellow)%cd %ad %Cred%h%Creset %s %Cgreen(%an)%Creset%C(bold blue)%d%Creset' --date=short --since="6 days ago" --reverse
echo
echo "finance repo:"
git -C finance log --format='%C(yellow)%cd %ad %Cred%h%Creset %s %Cgreen(%an)%Creset%C(bold blue)%d%Creset' --date=short --since="6 days ago" --reverse
echo
# show the sorted, unique files matched by SOURCEFILES
@_listsourcefiles:
for f in {{ SOURCEFILES }}; do echo $f; done | sort | uniq
# show the sorted, unique subdirectories containing hs files
@_listsourcedirs:
find . -name '*hs' | sed -e 's%[^/]*hs$%%' | sort | uniq
# Show a bunch of debug messages.
@_dbgmsgs:
rg --sort path -t hs 'dbg.*?(".*")' -r '$1' -o
# # Extract Hledger.hs examples to jargon.j.
# @_jargon:
# rg '^ *> (.*)' -or '$1' hledger-lib/Hledger.hs > jargon.j
# echo "wrote jargon.j"
# Extract ledger/hledger/beancount commit stats to project-commits.j.
@_projectcommits:
# https://hledger.org/reporting-version-control-stats.html
printf "account ledger\naccount hledger\naccount beancount\n\n" >project-commits.j
for p in ledger hledger beancount; do git -C ../$p log --format="%cd (%h) %s%n ($p) 1%n" --date=short --reverse >> project-commits.j; done
echo "wrote project-commits.j"