双向编解码器的Makefile

时间:2019-06-12 19:41:17

标签: makefile

从前,我编写了一个编解码器,以将IPython Notebooks(.ipynb)转换为“装饰的” Python代码,反之亦然。实践证明,这对于处理与笔记本相对应的源代码非常有用。在我的团队中,我们使用它来将经过修饰的Python源存储在我们的git repos中(而不是有时巨大的.ipynb文件中)。我们可以比较笔记本(与其他笔记本或沿版本图),在我们喜欢的IDE中对其进行重构,等等。我们通常在代码库中有一些示例笔记本作为示例和部分文档(它们在构建时就已混合并运行)文档)。

语法是:

nb2py example.ipynb -o example.py

py2nb example.py -o example.ipynb

现在,我想设置一个Makefile以更新所有.py.ipynb文件,以使最后更改文件形式的方式

例如,当我修改foo.py时,foo.ipynb将被更新以反映该更改。但是,如果我改为编辑foo.ipynb(大概是通过Jupyter),那么foo.py将被更新以反映该更改。

我有一个简单的Makefile,它几​​乎可以满足我的要求:

.PHONY: clean update nb py

# py --> nb
PY_SRC = $(wildcard *.py)
NB_DST = $(PY_SRC:.py=.ipynb)

# nb --> py
NB_SRC = $(wildcard *.ipynb)
PY_DST = $(NB_SRC:.ipynb=.py)

%.ipynb: %.py
    py2nb $< -o $@ && touch -r $< $@

%.py: %.ipynb
    nb2py $< -o $@ && touch -r $< $@

clean:
    tbd

update: nb py

nb: $(NB_DST)

py: $(PY_DST)

touch -r命令将目标文件设置为与源文件具有完全相同的时间戳,这样就不会在第二make内再次对其进行更新,并且也不会更新回来源。

我可以做:make nb,它会更新所有早于相应.ipynb的陈旧.py文件。我可以make py进行其他选择。 make有很多喘不过气,但是警告我有关整个设置的明显循环性。但这是对的。

update目标无法完全正常工作。 py --> nb方向有效,反之则不然。

最后,我不知道如何实现clean。举例来说,在我的上下文中,我希望clean删除将更新相应的.ipynb的任何.py(因为{{1 }}不存在,或者是因为它较旧。)

2 个答案:

答案 0 :(得分:2)

Make不是为此类事情设计的。它不喜欢循环依赖。尝试使用纯make解决该问题可能会创建一个难以维护的makefile。我将尝试创建一个工件文件,并使用Shell逻辑按如下所示进行复制:

.%.updated: %.py %.ipynb
    @if [ $*.py -nt $*.ipynb ]; then \
       py2nb $*.py -o $*.ipynb; \
    else \
       nb2py $*.ipynb-o $*.py;
    touch $@

该规则完成运行后,将创建.foo.updated,它将比.py.ipynb文件新,只要没有人修改这两个文件中的任何一个即可。有人进行修改后,它将立即按照正确的方向进行复制。

这假设.py.ipynb文件都存在。如果不是这种情况,则可以添加一些仅订购的依赖项来创建它们:

%.py : | %.ipynb
    touch -d "1 second ago" $@; touch $^

%.ipynb: | %.py
    touch -d "1 second ago" $@; touch $^

这些将使依赖关系比目标更新,然后落入.%.updated规则,该规则将以正确的方向运行副本。

答案 1 :(得分:2)

您可以考虑将其拆分为单独的Makefile,以隐藏make的基本圆度:


制作文件

update:
    $(MAKE) -f Makefile_py2nb
    $(MAKE) -f Makefile_nb2py

clean:
    $(MAKE) -f Makefile_py2nb clean
    $(MAKE) -f Makefile_nb2py clean

.PHONY: clean update

Makefile_nb2py

NB_SRC = $(wildcard *.ipynb)
PY_DST = $(NB_SRC:.ipynb=.py)

py: $(PY_DST)

clean:
    :

%.py: %.ipynb
    nb2py $< -o $@ && touch -r $< $@

.PHONY: py clean

Makefile_py2nb

PY_SRC = $(wildcard *.py)
NB_DST = $(PY_SRC:.py=.ipynb)

nb: $(NB_DST)

clean:
    for nb in $(NB_DST); do test -e $${nb} && test ! $${nb} -nt $${nb%%.*}.py && rm $${nb}; done

%.ipynb: %.py
    py2nb $< -o $@ && touch -r $< $@

.PHONY: nb clean

这也可以按照您描述的方式实现clean。请注意,对于您描述的清除模式,自然会(仅)在makefile中用于从.ipynb文件构建.py文件,因为这是前者是构建目标的一种,在这种情况下有意义以便对其进行清洁。该文件的结构排除了在该处处理的任何.ipynb文件,它们没有应有的相应.py文件。

您通常运行make,它会选择标准的Makefile。它将与其他每个makefile一起执行子make,每个makefile只能看到单向依赖项。