如何优化Makefile以避免重复

时间:2017-03-05 10:40:03

标签: makefile gnu-make

我是使用make的新手,并且一直在尝试了解如何正确配置make文件。特别是我认为有一种更好的方法来处理遵循模式的规则和依赖。但我无法真正理解这方面的手册。

我现在有一个工作的makefile(在OSX和Linux上,我假设使用gnu make),但是期望通过使用我不了解的技术可以大大缩短它。它有不同目录的重复模式。您能否告诉我如何更好地完成以下工作,让我知道每个起酥油使用哪种设施。

# Include this file NOT stored in repository which defines which environment to use
include Makefile.local
# Work out the pas version
PAS=${shell git describe --abbrev=0}
# Version of node being used
VERSION = 7.6.0

\.dockerimage: access/.dockerimage evening/.dockerimage server/.dockerimage
    touch .dockerimage
server/.dockerimage: client/client/.dockerimage server/.env $(shell find server  \! -name pacakge.json \! -name .dockerimage -depth 1 -type f)
    docker image build -t server:${PAS} -t server:latest server
    touch server/.dockerimage
server/.env: environments/common/server.env environments/${enviro}/server.env
    cat environments/common/server.env > server/.env
    cat environments/${enviro}/server.env >> server/.env
access/.dockerimage: request/.dockerimage access/.env $(shell find access  \! -name pacakge.json \! -name .dockerimage -depth 1 -type f)
    docker image build -t access:${PAS} -t access:latest access
    touch access/.dockerimage
access/.env: environments/common/access.env environments/${enviro}/access.env
    cat environments/common/access.env > access/.env
    cat environments/${enviro}/access.env >> access/.env
evening/.dockerimage: request/.dockerimage evening/.env $(shell find evening  \! -name pacakge.json \! -name .dockerimage -depth 1 -type f)
    docker image build -t evening:${PAS} -t evening:latest evening
    touch evening/.dockerimage
evening/.env: environments/common/evening.env environments/${enviro}/evening.env
    cat environments/common/evening.env > evening/.env
    cat environments/${enviro}/evening.env >> evening/.env
pcode/.dockerimage: libs/.dockerimage pcode/.env $(shell find pcode  \! -name pacakge.json \! -name .dockerimage -depth 1 -type f)
    docker image build -t pcode:${PAS} -t pcode:latest pcode
    touch pcode/.dockerimage
pcode/.env: environments/common/pcode.env environments/${enviro}/pcode.env
    cat environments/common/pcode.env > pcode/.env
    cat environments/${enviro}/pcode.env >> pcode/.env
test-client/.dockerimage: client/client/.dockerimage
    docker image build -t test-client:${PAS} test-client:latest test-client
    touch test-client/.dockerimage
test-server/.dockerimage: services/.dockerimage
    docker image build -t test-server:${PAS} -t test-server:latest test-server
    touch test-server/.dockerimage
client/client/.dockerimage: services/.dockerimage $(shell find client/client \! -name .dockerimage -type f)
    docker image build -t client:${PAS} -t client:latest client/client
    touch client/client/.dockerimage
services/.dockerimage: client/.dockerimage $(shell find services \! -name .dockerimage -type f -print0)
    docker image build -t services:${PAS} -t services:latest services  --build-arg PAS_VERSION=${PAS}
    touch services/.dockerimage
client/.dockerimage: libs/.dockerimage $(shell find client \! -name .dockerimage -depth 1 -type f)
    docker image build -t components:${PAS} -t components:latest client
    touch client/.dockerimage
libs/.dockerimage: request/.dockerimage $(shell find libs \! -name .dockerimage -type f)
    docker image build -t libs:${PAS} -t libs:latest libs
    touch libs/.dockerimage
request/.dockerimage: node/.dockerimage $(shell find request \! -name .dockerimage -type f)
    docker image build -t request:${PAS} -t request:latest request
    touch request/.dockerimage
node/.dockerimage: node/Dockerfile-${ARCH} node/.dockerignore
    docker image build -f node/Dockerfile-${ARCH} -t node:${VERSION} -t node:latest node
    touch node/.dockerimage
clean: clean-images clean-above clean-env
    rm node/.dockerimage
    docker image rm -f node:latest
    docker image rm -f node:${VERSION}
clean-above:
    for dir in server access evening pcode client/client services test-client test-server servies client libs request; \
        do rm $$dir/.dockerimage; done
clean-env:
    for dir in access evening pcode server; do rm $$dir/.env; done
clean-images: clean-above
    for dir in access access evening pcode client/client services test-client test-server services client libs request; \
     do docker image rm -f $$dir:latest;  docker image rm -f $$dir:${PAS}; done
.PHONY: run clean clean-above clean-images clean-env

在Windows上运行git-bash(在其上添加make.exe)也很有用,虽然不是必需的。

3 个答案:

答案 0 :(得分:1)

StackOverflow实际上并不是要求某人重写代码的地方。

我会说两件事:首先,为了减少重复,你需要了解make variables:你可以在变量中放入很多重复的项目,然后使用它们而不是多次写入所有内容。这很简单。

其次,你应该研究implicit rules,尤其是pattern rules。看起来您的规则之间存在足够的重叠,您应该能够创建一些不同的模式规则,而不是为每个目标编写单独的显式规则。这是一个稍微高级的话题。

您可以尝试修改您的makefile并查看会发生什么:尝试和错误通常是学习的最佳方式,而且便宜且易于多次运行make。如果你遇到问题就无法解决,现在你有一个适合StackOverflow的特定问题:)。

哦,最后一件事:您应该考虑使用simply expanded个变量(例如,使用:=进行分配而不是=),尤其是对于$(shell ...)次调用;它会更有效率。

答案 1 :(得分:0)

简化makefile可以在迭代时提供良好的结果。从较小的步骤开始,在函数/变量/隐式规则中包含重复的模式,并一次又一次地执行。 以下是我尝试简化makefile中的一些规则:

some_prerequisites = $1/.env $(shell find $1 \! -name pacakge.json \! -name .dockerimage -depth 1 -type f)

environment_recipe = cat environments/common/$1.env environments/${enviro}/$1.env > $@
dockerimage_recipe = docker image build -t $1:${PAS} -t $1:latest $1 && touch $@

\.dockerimage: access/.dockerimage evening/.dockerimage server/.dockerimage
    touch $@
server/.dockerimage: client/client/.dockerimage $(call some_prerequisites, server)
    $(call dockerimage_recipe,server)
server/.env: environments/common/server.env environments/${enviro}/server.env
    $(call environment_recipe,server)
access/.dockerimage: request/.dockerimage $(call some_prerequisites, access)
    $(call dockerimage_recipe,access)
access/.env: environments/common/access.env environments/${enviro}/access.env
    $(call environment_recipe,access)
evening/.dockerimage: request/.dockerimage $(call some_prerequisites, evening)
    $(call dockerimage_recipe,evening)
evening/.env: environments/common/evening.env environments/${enviro}/evening.env
    $(call environment_recipe,evening)
pcode/.dockerimage: libs/.dockerimage $(call some_prerequisites, pcode)
    $(call dockerimage_recipe,pcode)
pcode/.env: environments/common/pcode.env environments/${enviro}/pcode.env
    $(call environment_recipe,pcode)

在第一次迭代中,我引入了some_prerequisites函数。然后,当makefile变得稍微不那么混乱时,我添加了environment_recipedockerimage_recipe

这是#2迭代:

some_prerequisites = $1/.env $(shell find $1 \! -name pacakge.json \! -name .dockerimage -depth 1 -type f)

environment_recipe = cat environments/common/$1.env environments/${enviro}/$1.env > $@
dockerimage_recipe = docker image build -t $1:${PAS} -t $1:latest $1 && touch $@

\.dockerimage: access/.dockerimage evening/.dockerimage server/.dockerimage
    touch $@
server/.dockerimage: client/client/.dockerimage $(call some_prerequisites, server)
    $(call dockerimage_recipe,server)
access/.dockerimage: request/.dockerimage $(call some_prerequisites, access)
    $(call dockerimage_recipe,access)
evening/.dockerimage: request/.dockerimage $(call some_prerequisites, evening)
    $(call dockerimage_recipe,evening)
pcode/.dockerimage: libs/.dockerimage $(call some_prerequisites, pcode)
    $(call dockerimage_recipe,pcode)

$(foreach e, pcode evening access server \
    $(eval $e/.env: environments/common/$e.env environments/${enviro}/$e.env; \
        $$(call environment_recipe,$e) \
    ) \
)

这是#3迭代:

some_prerequisites = $1/.env $(shell find $1 \! -name pacakge.json \! -name .dockerimage -depth 1 -type f)

environment_recipe = cat environments/common/$1.env environments/${enviro}/$1.env > $@
dockerimage_recipe = docker image build -t $1:${PAS} -t $1:latest $1 && touch $@

\.dockerimage: access/.dockerimage evening/.dockerimage server/.dockerimage
    touch $@
server/.dockerimage: client/client/.dockerimage
access/.dockerimage: request/.dockerimage
evening/.dockerimage: request/.dockerimage
pcode/.dockerimage: libs/.dockerimage

$(foreach e, pcode evening access server \
    $(eval $e/.env: environments/common/$e.env environments/${enviro}/$e.env; \
        $$(call environment_recipe,$e) \
    ) \
    $(eval $e/.dockerimage: $(call some_prerequisites, $e); \
        $$(call dockerimage_recipe,$e) \
    )\
)

在我看来,这足以简化makefile中的一些规则。其他可以按照相同的模式进行简化。

答案 2 :(得分:0)

感谢@MadScientist,我一直在探索隐式模式。我有一个扩展的问题,在另一个SO问题/答案中解决了。我认为为了完整起见,我会发布我已经到达的地方。从最初的问题开始,我为makefile添加了很多选项,所以它不是一对一的比较。

# Include this file NOT stored in repository which defines which environment to use
include Makefile.local
#work out architecture from envonment
ARCH := ${shell cat environments/${enviro}/arch}
# Work out the pas version
PAS := ${shell git describe --abbrev=0}
# None standard dependancies for each of the images
server-IMAGEDEPS := libs/database/index.js libs/log/index.js libs/utils/index.js\
    $(shell find services/manager -type f -not -name package.json -print0) $(shell find services/web -type f -not -name package.json) \
    $(shell find client -type f -not -name .bowerrc -not -name bower.json -not -name package.json -not -name DOCKBUILDfile -print0)
server-BASEDEPS := services/.dockerimage
server-LINKDEPS := pasv5-database pasv5-manager pasv5-web
access-IMAGEDEPS := libs/request/index.js
access-BASEDEPS := libs/.dockerimage
access-LINKDEPS := pasv5-request
evening-IMAGEDEPS := libs/request/index.js libs/log/index.js
evening-BASEDEPS := libs/.dockerimage
evening-LINKDEPS := pasv5-request pasv5-log
pcode-IMAGEDEPS := libs/database/index.js libs/log/index.js libs/utils/index.js
pcode-BASEDEPS := libs/.dockerimage
pcode-LINKDEPS := pasv5-database pasv5-log pasv5-utils
manage-LINKDEPS := pasv5-utils pasv5-log
web-LINKDEPS := pasv5-log
daily-LINKDEPS := pasv5-database

LINKMODULEDIRS := libs/database libs/log libs/utils libs/request services/manager services/web
NODEMODULEDIRS := $(LINKMODULEDIRS) access evening pcode server daily

# Standard docker build command
DOCKBUILD = docker image build -t $(@D)-base:stable $(@D); touch $@

all: server/.dockerimage evening/.dockerimage pcode/.dockerimage access/.dockerimage

daily/.env: pcode/.env
    cp pcode/.env daily/.env
services/.dockerimage: client/.dockerimage services/Dockerfile services/package.json services/manager/package.json services/web/package.json
    @(DOCKBUILD)
client/.dockerimage: libs/.dockerimage client/Dockerfile client/package.json client/.bowerrc client/bower.json
    @(DOCKBUILD)
libs/.dockerimage: node/.dockerimage libs/Dockerfile libs/package.json libs/database/package.docker libs/log/package.json libs/utils/package.json \
    libs/request/package.json libs/request/akc-crt.pem
    @(DOCKBUILD)
node/.dockerimage: node/Dockerfile-${ARCH} node/.dockerignore
    docker image build -f node/Dockerfile-${ARCH} -t node:latest node
    $(eval VERSION := $(shell docker run node:latest node --version | cut -c 2- ))
    docker image tag node:latest node:${VERSION}
    docker image tag node:latest docker.hartley-consultants.com/node:${VERSION}
    touch $@
# stable version of node
node-stable:
    docker image tag node:latest node:stable
clean: clean-images clean-above clean-env
    rm node/.dockerimage
    docker image rm -f node:latest
    docker image rm -f node:${VERSION}
clean-above:
    for dir in server access evening pcode client services libs; do [ -f $$dir/.dockerimage ] && rm $$dir/.dockerimage; [ -f $$dir/.dockerbase ] && rm $$dir/.dockerbase; done; exit 0
clean-env:
    for dir in access evening pcode server; do rm $$dir/.env; done
clean-images: clean-above
    for img in access evening pcode server; \
     do docker image rm -f $$img-base:stable;  docker image rm -f $$img-${enviro}:${PAS}; rm $$img/.dockerimage; rm $$img/.dockerbase; done
    for img in services client libs; do docker image rm -f $$img:stable; rm $$img/.dockerimage; done

# sets up to run system without docker images
local: server/.env evening/.env pcode/.env access/.env daily/.env $(foreach dir,$(NODEMODULEDIRS), $(dir)/node_modules)
    npm install -g bower && cd client && bower install

pasv5-database:
    cd libs/database; npm link
pasv5-log:
    cd libs/log; npm link
pasv5-utils:
    cd libs/utils; npm link
pasv5-request:
    cd libs/request; npm link
pasv5-manager:
    cd services/manager; npm-link
pasv5-web:
    cd services/web; npm-link
clean-local:
    $(foreach dir,$(NODEMODULEDIRS), $(shell rm -rf $(dir)/node_modules))
.SECONDEXPANSION:
# pattern rules
%/.env: envionments/common/%.env environments/${enviro}/%.env; cat $^ > $@
%/.dockerbase: %/Dockerfile %/package.docker %/.env $$(%-BASEDEPS)
    $(DOCKBUILD)
%/.dockerimage: %/.dockerbase %/server.js Dockerfile-% $$(%-IMAGEDEPS)
    docker image build -t $(@D)-${enviro}:${PAS} --build-arg PAS_VERSION=${PAS} -f Dockerfile-${@D} .
ifeq ($(enviro), production)
    docker tag $(@D)-production:${PAS} docker.hartley-consultants.com/pas/$(@D):${PAS}
endif
    touch $@
%/node_modules: $$(%-LINKDEPS)
    cd $*; npm install; for link in $^ ; do npm link $$link ; done


.PHONY: all clean clean-above clean-images clean-env node-stable local clean-local pasv5-database pasv5-log pasv5-utils pasv5-request pasv5-manager pasv5-web