Making things easier to do makes me more likely to do them. Even a simple static website like this one requires a few bespoke commands to build and deploy, so to lower the barrier to publish, I decided to start learning and using GNU Make to reduce the steps to one command.
Make is a program designed for generating executables (or any end product) from a set of source files. You define a set of rules which must be executed to generate your target 'thing'. Make will re-build your target if one of the pre-requisite files changes to keep it up to date. Make is quite old and a very nice piece of software. I won't be using any of the target-file prerequisite rules here, instead I will be using Phony Targets (learn X in Y minutes has a nice make introduction).
Phony targets are rules which do not have targets, so they are never satisfied and always run (see Phony Targets explanation here).
Which rules do we need?
My workflow involves writing
org-mode source files, compiling those to
HTML before viewing, building the site, and pushing to GitHub pages.
I need rules to do the following:
compile any new
orgsource files to
start a server to view the website
build website (using stasis)
run git commands to push it to GitHub pages.
Make runs shell commands, so the first things we need to do is figure out how to use emacs and
org-mode to compile my website project from the command line.
org-publish from the shell
emacs -batch -eval '(message "hello from emacs lisp")' # "hello from emacs lisp"
-batch flag causes emacs to run in non-interactive mode and without loading an
init file (https://www.emacswiki.org/emacs/BatchMode). It is typically used to run commands or emacs-lisp files as scripts. The
-eval flag tells emacs to evaluate whatever lisp expression follows.
To publish this site with org-mode, I run
M-x org-publish and select
clj-site. The commands for
clj-site are defined in my init file. To run this from the command line, we first need to set the variable
org-publish-project-alist. While we could pass that as a string to
-eval, it would be easier to define all the settings and variables I need in a small elisp file. This can be done in batch mode using the
-load flag, followed by a file. I will copy my variable declaration from my init file and add a few other settings like so:
;; publish org-mode project from makefile ;; based on: https://stackoverflow.com/questions/46295511/how-to-run-org-mode-commands-from-shell ;; (add-to-list 'load-path "~/.emacs.d/elpa/org-20191125") (add-to-list 'load-path "~/.emacs.d/elpa/") (add-to-list 'load-path "~/.emacs.d/manual-packages") (require 'org) (require 'ox-publish) (require 'ox-html) ;;(require 'htmlize) (setq org-src-fontify-natively t) (load-theme 'leuven t) (setq org-publish-project-alist '(("programming" :base-directory "~/personal_projects/website-clj/resources/org-programming" :base-extension "org" :publishing-directory "~/personal_projects/website-clj/resources/programming" :publishing-function org-html-publish-to-html :headline-levels 4 :html-extension "html" :body-only t) ("science" :base-directory "~/personal_projects/website-clj/resources/org-science" :base-extension "org" :publishing-directory "~/personal_projects/website-clj/resources/science" :publishing-function org-html-publish-to-html :headline-levels 4 :html-extension "html" :body-only t) ("clj-site" :components ("programming" "science"))))
I named this file
publish.el, and it lives in the base directory of the website (the place where I will run
make from). Compiling documents in batch mode looks like this (from the base directory of my website):
emacs -batch -load publish.el -eval '(org-publish "clj-site")'
That was the hard part, now we just need to write some rules for make to run. We will name the first run update:
# Makefile update: @echo "updating site..." emacs -batch --load publish.el --eval '(org-publish "clj-site")'
The rule is called
@ prefix on the
echo command stops the text of the command from being echoed to stdout before it is executed.
We can now update the site by running:
I start the ring web server for previewing my app by running
lein ring server. To view my site before deploying, we can add the following command:
# Makefile update: @echo "updating site..." emacs -batch --load publish.el --eval '(org-publish "clj-site")' view: @echo "updating site..." emacs -batch --load publish.el --eval '(org-publish "clj-site")' @echo "Starting server to view website" lein ring server
make view will now update then display the site.
Deploying using make
Deploying is slightly more complicated. To deploy, I need to run
lein build-site, commit and push the changes,
cd to the
target/ directory and again commit and push the website changes.
Make spawns a new shell for every line, but to run git commands we need to be in the appropriate directory. We can work with this by running all the commands in one line:
# Makefile update: @echo "updating site..." emacs -batch --load publish.el --eval '(org-publish "clj-site")' view: @echo "updating site..." emacs -batch --load publish.el --eval '(org-publish "clj-site")' @echo "Starting server to view website" lein ring server deploy: @echo "deploying site." @echo "Updating now from emacs..." emacs -batch --load publish.el --eval '(org-publish "clj-site")' # this will build and deploy the entire site @echo "building and pushing via git..." lein build-site;git add .;git commit -m "content update";git push;cd target/nickgeorge.net/; git add .;git commit -m "automated commit."; git push @echo "Done!"
With that we have our final rule,
Now I can run
make deploy from the command line to publish my website, rather than running each command individually.