Pages

Saturday, 30 July 2016

用Makefile搭建静态博客

自己写 Makefile

作为一个 Emacs 用户,得用 Org Mode 做标记语言,其实通过配置 org-publish 应该也能实现的。但我不大会 Elisp,也不熟悉 Org Mode,所以要找其他工具来做发布的任务。我发现了一个叫做 blorgit 的博客系统,跟着教程一步步做,默认页面不怎么样。而且它是 Ruby 写的,我配置不来,于是这个也放弃了。最后选择了用 Makefile 自己写。

基本思路

.org 存放实际写的文章,用于生成 .html,首页 index.html 是文章列表。还要使用第三方的评论系统。

设计

目录设计如下: - 创建目录 2011/07/12/ 来表示当天写的文章(当前还有其他日期,这里使用 2011/07/12/ 只是为了方便阐述) - 2011/07/12/ 下创建 blogging-with-makefile.org ,用于生成 blogging-with-makefile.html - 2011/ 下创建 titles-07 缓存2011年7月所有文章的标题
文章部分就用如上形式储存。所有文章都有共用的页头、页脚(我不知道准确的术语),比如本页上方的的链接和下方 disqus 的评论系统,它们分别储存为 header.org 和 footer.org 。当要把某篇文章的 .org 导出为 .html 时,在前面包含 header.org ,后面包含 footer.org 。 disqus 要求每个网页要包含几个特定的变量,所以 footer.org 需要做下修改再包含。
首页是所有文章的列表,需要用脚本生成出来,还要注意的是它不需要评论系统,所以不包含 footer.org ,

Makefile

Makefile 的主要规则如下:
  • 2011/titles-07 依赖 2011/07/ ,如果该目标过期(需要重建), 那么尝试重建 2011/07/12/ 下所有日期的所有 .html 。然后把这些 .html 的标题抽取出来, 写入 2011/titles-07
  • %.html 依赖 %.org 以及页头页脚,需要用 Org Mode 根据 .org 生成 .html。 这个规则也对首页 index.org 有效。
当 2011/07/12/ 创建后, 2011/titles-07 就会过期,重建 2011/07/12/ 下所有日期的所有过期的 .html ,并且生成新的 2011/titles-07 ,进而 重建 index.org ,最后重建 index.html 。
注意到这种设计把目录作为了依赖,我们要知道目录的修改时间是不会因为被它包含的文件的修改而改变的。所以当 2011/07/12/blogging-with-makefile.org 修改之后, 2011/07/12/ 和 年/月/ 的修改时间都是不会变化的。这样就会导致 2011/07/12/blogging-with-makefile.org 修改后,执行 make 时 2011/07/12/blogging-with-makefile.org 不会得到更新。
为了处理这种情况,我们需要让目录随着被它包含的文件的修改而更新修改时间。我想到的办法是用 inotify,当 2011/07/12/blogging-with-makefile.org 修改之后,自动 touch 年/月/ 。

from http://maskray.me/blog/2011-07-12-blogging-with-makefile
----------


前一篇用Makefile搭建博客说到我用inotifywait监控目录下文件的写操作来更新目录的修改时间,以后只要把make inotify放到启动脚本中就行了。但当时就发现了一个问题,一直拖到今天才解决掉。

GoboLinux Scripts包中的ColorMake

先从/ColorMake/说起。/GNU Make/的颜色是很单调的,我一般用GoboLinux的包Scripts里的ColorMake来给make上色。而这个ColorMake实际上是写了一个mtail的配置文件ColorMake.mtailrc,把make的输出管道给mtail来上色。可以参看。我之前则是用的http://bre.klaki.net/programs/colormake/里的colormake.pl脚本来上色的。
我把ColorMake.mtailrc保存为 ~/bin/ColorMake.mtailrc ,另外写了个 wrapper,保存到 ~/bin/mk ,内容如下:
#!/bin/sh
/usr/bin/make "$@" 2>&1 | mtail -q --config ~/bin/ColorMake.mtailrc
意思就是把mk的参数全部传递给makemake的 stdout stderr 全部管道给mtail来上色。
另外, ~/bin 在我的环境变量PATH中。

产生问题的命令

先来看我的 [[/Makefile][Makefile]],只要看 inotify 伪目标,其他的可以忽略掉。之前 inotify 的规则是 inotifywait -e modify -m -r . –format %w | xargs -I % sh -c “touch `dirname %`” &
make inotify 运行得非常正常,inotifyxargs在后台执行;但如果执行mk inotify,问题就来了,终端会被占着,无法再执行其他命令了。

分析

make inotify

先来看执行make inotify会发生什么,不妨假设交互用的shell是Zsh,这里用Bash效果也是一样的。
-zsh进程产生一个make进程 -make进程执行重建 inotify 的规则,即产生一个进程执行inotifywait -e modify -m -r . --format %w | xargs -I % sh -c "touch \`dirname %\`" &
不妨用 /Lisp/ 来表示进程树,那么这些进程的关系如下: (zsh (make (inotifywait) (xargs))) 。 一对圆括号代表进程,圆括号第一个元素是进程名,其余元素代表子进程
接着,
-make退出,因为规则执行完了 -zsh检测到它的子进程make退出,又可以执行其他命令了

mk inotify

-zsh进程产生mk,其实是用/bin/sh解释mk,这里就简写成mk -mk产生makemtail,其中管道的一端是make的fd1、fd2,另一端是mtail的fd0 -make产生inotifywaitxargs
第三步中,make的文件描述符被inotifywaitxargs继承, 由于inotifywaitxargs用另一根管道而把 fd1 关闭了, 所以现在原管道的两端分别是:
-make的fd1、fd2;inotifywait的fd2;xargs的fd1、fd2 -mtail的fd0
现在的进程树是: (zsh (mk (make (inotifywait) (xargs)) (mtail)))
接着,
  • make退出,因为规则执行完了
  • 因为管道的写端描述符没有全部关闭,mtail不会读到EOF退出,而是等待管道读端的数据
  • mk也不会退出,因为它的某个子进程mtail没有退出
  • zsh未检测到mk的退出,所以终端被占用了,没法执行其他命令

解决方案

由前面的分析可以看出,只要让mtail退出,那么mk会跟着退出,终端就不会被占用了。 而要让mtail退出,就要让它读到 eof 退出,我们只要让make产生进程时不要把管道的写端描述符 传递给inotifywaitxargs,但是这个据我所知是做不到的。 但我们可以让inotifywaitxargs立刻把相应写端描述符关闭, 这个很简单,用
1
inotifywait -e modify -m -r . --format %w 2>&- | xargs -I % sh -c "touch \`dirname %\`" >&- 2>&- &
代替原来的命令就行了.

from  http://maskray.me/blog/2011-07-19-blogging-with-makefile-2