二、写规则(Rules)

规则没有先后顺序

一般来说规则的顺序是没有先后的,除了默认target的规则。make会将第一个makefile里面的第一条规则的第一个target作为默认的target。所以,默认target的规则应该放在最前面,一般使用all作为默认target的名称。

规则的声明

第一种格式,将recipe从新行开始写,如下:

targets : prerequisites
	recipe
	...

第二种格式,从prerequisites列表后面开始写recipes,用[ ; ]隔开,如下:

targets : prerequisites ; recipe
	recipe
	...

target可以有多个,使用空格分开,通常来说一个规则里面只有一个target,也不排除有其它特殊的情况(比如后面要讲的使用了通配符,或者静态模板规则)。

规则里面可以$来引用变量,如果只是想单纯的使用$符号,必须使用$$,因为targets和prerequisites都会在read-in阶段被展开,一个$的话会被直接当作变量引用进行展开。如果使用.SECONDEXPANSION开启了prerequisites的二次展开,而你又想在prerequisites部分使用$符号,则应该使用$$$$(第一次展开去掉第一个,第三个$,第二次展开去掉第二个$,最后剩下一个单纯的$符号)。

在make的第二阶段,make会对target的prerequisites列表里面的项逐个进行检查,看是否需要更新target。如果某个target不存在,或者某些prerequisites有更新的话,则该target会被生成或更新,这样的presiquisites叫做常规presiquisites,它的更新会导致依赖它的target也会被更新。
但是有时候,我们仅仅是希望某些prerequisites存在即可,不希望它们影响到target的更新。比如说像目录这样的prerequisites,我们可能希望运行target的recipe之前存在某个目录以方便存放一些临时文件,但是这个目录是否更新我们并不关心,我们只是希望它存在即可。这样的prerequisites叫做Order-only prerequisites,检查target的时候,其只参与存在与否的检测,不参与是否比target更新的检测。

在prerequisites列表中,使用[ | ]来分割常规prerequisites和Order-only prerequisites。[ | ]左边的为常规类型,右边的为Order-only类型:

targets : normal-prerequisites | order-only-prerequisites

使用通配符

我们可以在规则里面使用通配符,使用通配符得到的结果是经过排序的。make里面的通配符是[ * ],[ ? ]和[ … ]。[ ~ ]也有特殊含义,表示用户的家目录。

targets和prerequisites部分的通配符展开是由make来负责的,recipe部分的通配符展开由shell负责。其它地方应通过wildcard函数来使用通配符,不然通配符会被当作一个普通的字符处理。如果想在Rule里面单纯使用[ * ]符号,则应使用转义[ \ ]进行转义,比如foo\*bar

使用通配符要注意的地方

当通配符没有配到到任何结果时,则返回它本身,比如*.o,如果查找目录下没有.o文件,则*.o的结果就是"*.o",这通常并不是我们想要的结果。我们可以通过一些更成熟的方法来避免这样的问题,比如说使用wildcard函数和字符串替换,这些方法后面再进行学习。

使用wildcard函数

前面讲了Rule里面的通配符可以直接由make或shell展开,但是其它部分的通配符(比如赋值部分出现的通配符,或者函数参数里面的通配符)并不能自动展开,需要借助wildcard函数,其使用形式如下:

$(wildcard pattern...)

匹配到的条目排序后以空格分开,如果没有匹配到任何条目,则函数直接被忽略,不会返回任何东西。

VPATH变量,指定Prerequisites的查找路径

make的查找路径默认为Makefile所在的目录,通过使用VPATH变量,可以设置其它查找目录(目录使用冒号或空格分开),查找顺序是当前目录 > VPATH指定的路径,找到prerequisite后则停止查找。这样,我们可以将prerequisites集中放在几个目录里面,通过VPATH变量来指定这些目录的路径,就不需要在Rule里带上具体的路径了。

实际上,make也会通过VPATH去查找target。

使用vpath指令设置匹配文件的查找路径

vpath directive比VPATH变量更进一步,它可以以文件名匹配的方式设置查找路径,其使用方式如下:

vpath pattern directories

pattern是要匹配的文件名,使用[ % ]来匹配零个或多个任意字符。directories是设置的查找目录,用冒号或空格隔开。查找顺序为当前目录 > 设置的目录,比如有如下的makefile片段:

vpath %.c foo:bar
vpath % blish

对于当前目录下不存在的.c文件,会依次去foo bar blish目录下查找。对于当前目录下不存在的其它文件,则会再去blish目录下查找。

vpath directive还有其它两种使用方式如下:

vpath pattern

表示清除掉使用vpath为pattern设置的查找路径。

vpath

表示清除掉所有通过vpath设置的查找路径。

make是如何使用搜索目录的?

前面两部分说了可以通过VPATH变量或vpath directive来指定target和prerequisites的额外查找路径。对于target文件来说,还存在一个问题就是是否确实要使用查找到的路径。当我们在额外路径下面找到了target文件时,如果发现其是最新的,不需要重构,那么会直接将它作为其它target文件的prerequisite。如果它需要进行重构,那么make会抛弃找到它的路径,优先使用Rule里面指定的路径(也就是会在Rule里指定的路径下生成新的target)。
可以通过GPATH变量来为指定目录定制这种行为,通过GPATH变量指定的目录,作为被查找到的路径,会被保留使用(新的target会生成在查找到的目录里面)。

当我们在makefile里面使用了目录搜索的功能时,需要小心编写Rule里面的Recipe,以便shell能够通过正确的路径找到文件。这可以通过使用自动变量来实现,自动变量会包含文件所在的具体路径。比如,有如下的makefile片段:

VPATH = src:../headers
foo.o : foo.c defs.h hack.h
cc -c $(CFLAGS) $< -o $@

如果在src目录下找到了foo.c,则$<src/foo.c;如果在../headers目录下找到了foo.c,则$<../headers/foo.c。相对路径都是以makefile所在目录为起点。

隐式规则里面的目录查找

目录查找也适用于隐式规则,比如对于foo.o目标的隐式规则,会使用foo.c来生成foo.o。当前目录下没有foo.c的时候,就会去vpath和VPATH指定的目录下去查找foo.c。隐式规则的Recipe里面使用的通常都是自动变量,所以能正确引用foo.c的路径。

链接库的目录查找

当prerequisites列表里面使用-lname的形式指定了依赖库的时候,也会对其进行目录查找,优先使用动态库libname.so,然后是静态库libname.a。查找顺序是当前目录 > vpath指定的目录 > VPATH指定的目录 > 系统库目录(/lib, /usr/lib, and /usr/local/lib)。

伪目标(Phony Targets)

伪目标并不对应一个真正的文件,其只是用于明确告诉make去执行一个规则,而不是创建伪目标这个文件。比如有如下的伪目标定义:

clean:
	rm *.o temp

一般来说,目录下不会存在与伪目标同名的clean文件,所以每次去make伪目标的时候,对应的Recipies都会被运行。但是当目录下确实存在一个同名文件的时候,这个规则就不能得到预期的效果,因为规则里没有presiquisites,所以target总是最新的,因此recipe总是得不到运行。为了避免这样的问题,我们可以明确指定clean为一个Phony Target,方法是将clean作为.PHONY这个特殊目标的依赖,如下:

.PHONY: clean
clean:
rm *.o temp

这样,每次make clean的时候,不管是否存在clean这个文件,规则里面的recpies都会运行。

.PHONY目标的依赖都是直接被当作文件名,不进行通配展开。
make不会对伪目标进行隐式规则匹配。
伪目标不应该作为其它真实目标的prerequisite,不然目标总是会被更新。
伪目标可以包含prerequisites列表,比如我们常用的all:

all : prog1 prog2 prog3
.PHONY : all
prog1 : prog1.o utils.o
cc -o prog1 prog1.o utils.o
prog2 : prog2.o
cc -o prog2 prog2.o
prog3 : prog3.o sort.o utils.o
cc -o prog3 prog3.o sort.o utils.o

这样,我们可以使用一个makefile编译出3个程序的可执行文件。

没有Prerequisites或Recipes的规则

如果这种规则的target不是一个存在的文件,则make将Rule的运行等同于target的更新。这样的话,任何将这个target当作prerequisite的target的Recipies总是会被运行。比如,有如下的makefile片段:

clean: FORCE
	rm $(objects)
	
FORCE:

对于目标FORCE的Rule,没有prerequisites和recipes,并且FORCE也并不是一个文件(可以近似看作一个伪目标,只是没有用.PHONY声明)。当运行make clean的时候,去检查其依赖FORCE。因为FORCE不存在,并且规则里面也没有对应的Recipes去生成它,所以make直接将其视为更新状态。这样,clean也会被视为需要进行更新,其Recipes会被执行。通常用这种方法对某个目标进行强制更新;或者强制运行某个Rule里面的Recipes(这种情况跟使用.PHONY的方法效果相同,对于一些不支持.PHONY的make程序,就使用FORCE来实现相同的效果)。

使用空目标文件来记录事件

有时候一个规则不是为了生成一个具体的target文件,而是为了执行一组命令,比如打印改变了的文件。这样,我们需要记录上次打印的时间,以免重复打印,可以专门将target作为记录打印事件的用途。假设有如下的makefile片段:

print: foo.c bar.c
	lpr -p $?
	touch print

这条规则用于在foo.c或bar.c被更新的时候打印对应的文件。print并不是一个规则需要生成的target文件,其只是在打印后被更新,用于表示更新的文件已经被打印过了,下次再运行make print便不会重复打印。

一条规则里面出现多个target

一条规则里面出现多个target的时候,make以两种不同的方式对待这些target:把它们当作相互独立的target或将它们当作成组的target。

当作独立的target是指相当于将每个target都拆分成相同的规则,有同样的prerequisites列表和recipes,自动变量$@用于表示当前的target。

当作成组的target是指这些target都是在规则的recipes里同时生成的,此时自动变量$@表示的是触发Recipies被执行的那个target。成组的target总是会同时更新,哪怕只有一个target过期,其它的target也会被一起更新。

targets : prerequisites表示targets是相互独立的。
targets &: prerequisites表示targets是成组的。
[ &:: ]用于在多个组里面包含同一个target文件,这种情况就不讨论了。

多条规则对应同一个target文件

一个文件可以作为多条规则的目标文件,这个目标对应的所有规则里面的所有prerequisites文件会合并成一个prerequisites列表来作为这个target的prerequisites,但是只能运行一个recipe来更新这个target。当有一个以上的规则里面都有recipe的时候,make会使用最后一条规则的recipe并打印一个错误消息(对于使用[ . ]开头的target,不会打印这个错误)。如果这些规则里面都没有recipe,则尝试使用合适的隐式规则来更新target。

通常来说,将多条规则对应同一个target文件是为了方便分多次为target添加prerequisites文件,recipe可以写在任意一条规则里面,比如下面的例子:

objects = foo.o bar.o
foo.o : defs.h
bar.o : defs.h test.h
$(objects) : config.h

config.h同时作为foo.o和bar.o的依赖,使用一条规则同时把它添加到foo.o和bar.o的prerequisites列表里面。

静态模板规则(Static Pattern Rules)

前面讲的规则都是普通规则(包括显式规则和隐式规则),在一条普通规则里面,可以有多个target文件,这些target文件共享规则的prerequisites列表。因为这个缘故,当某些targets有部分共享的prerequisites时,可以通过这种方式将这些targets写在一个规则里面。但是对于其它不是共享的prerequisites,则需要为target再单独增加规则进行指定,这就出现一个target会有多条规则的情况。

出现这个情况的根本原因在于将多个target写在一个规则里面时,它们会共享规则的prerequisites列表(哪怕这些target其实并不依赖于某些prerequisites)。静态模板规则就是为了解决这个问题而提出来的,我们可以将多个target文件写在一条静态模板规则里面,但是prerequisites列表不是写死的,而是使用静态模板匹配的方式分别生成这些targets的prerequisites列表。静态模板规则的书写格式如下:

targets ...: target-pattern: prereq-patterns ...
	recipe
	...

targets列表中的每个target文件都会与target-pattern模板进行匹配(对于没有进行匹配的target,会给出一个警告信息),匹配符是[ % ],target文件名中被[ % ]匹配到的部分叫做主干(stem)。通常来说,pattern中只有一个匹配符。主干替换掉prereq-patterns模板里面的[ % ]就得到这个target文件的prerequisites列表。

从静态模板的匹配方式可以看出,虽然可以在一个静态模板规则里面为不同的targets生成不同的prerequisites列表,但是这些targets文件的名称必须相似,否则无法使用target模板来做主干提取;这些prerequisites文件的名称也必须相似,否则无法使用prereq模板来做主干替换。

在prereq-patterns里面直接写死prerequisite文件名也是合法的,这个文件会成为所有target的prerequisite。

假设有如下的makefile片段:

objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
	$(CC) -c $(CFLAGS) $< -o $@

对于foo.o,[ % ]匹配到的主干是"foo",因此其prerequisites列表是foo.c;对于bar.o,[ % ]匹配到的主干是"bar",因此其prerequisites列表是bar.c;

假设有如下的makefile片段:

bigoutput littleoutput : %output : text.g
	generate text.g -$* > $@

这里text.g会同时作为bigoutput和littleoutputd的prerequisite文件,recipe中的自动变量$*表示匹配到的主干,对于bigoutput目标,$* = “big”;对于littleoutput目标,$* = “little”。

隐式规则和静态模板规则的比较

这两种规则有点类似,都是通过使用模板匹配的方式,通过target文件名来生成对应的prerequisites文件列表的,它们的区别在于make怎么决定什么时候应用规则。

当没有为target指定recipe,并且target的prerequisites文件可以找到时,才会应用隐式规则。如果有多条匹配的隐式规则可用时,只会按顺序选择一条规则来应用。
而静态模板规则会直接应用到指定的targets,如果有两条规则冲突,并且都指定了recipe,会报错。

一般来说,静态模板规则更优于隐式规则,因为我们可以在规则里面指定匹配方式并且可以直接给出recipe。而隐式规则对我们来说其实并不具体(特别是我们不太清楚目录下有些什么东西时),我们不知道它具体匹配到了什么prerequisites,使用了什么recipe,这些不确定性因素的引入可能会埋下一些潜在的隐患。

  • 33
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Element UI 是一个基于 Vue.js 的前端 UI 框架,它提供了丰富的表单验证规则。 一些常用的验证规则包括: - required: 必填项 - min: 最小长度 - max: 最大长度 - min-value: 最小值 - max-value: 最大值 - pattern: 正则表达式 - email: 邮箱格式 - url: URL 格式 - date: 日期格式 - number: 数字格式 在使用时,可以在表单元素上使用 :rules 属性来定义验证规则,如: ``` <el-form-item label="用户名" prop="username"> <el-input v-model="form.username" :rules="[{ required: true, message: '请输入用户名', trigger: 'blur' }]"></el-input> </el-form-item> ``` 在上面的示例中,我们为用户名输入框定义了一个必填验证规则。 更多细节和用法可以参考官方文档: https://element.eleme.cn/#/zh-CN/component/form ### 回答2: element表单验证规则rules是一种用于实现表单验证的方式,通常用于前端开发中。在web开发过程中,表单是一个很重要的组件,因为它允许用户向后台服务器提交数据。为了确保数据的准确性和完整性,需要对表单进行验证,并将不符合要求的数据返回给用户以便修正。 在element组件库中,rules是用于定义表单验证规则的方式之一。通过定义合适的rules,可以实现对输入内容的要求,例如是否为必填项、长度要求、格式验证等等。常见的rules规则包括:required(必填)、email(验证邮箱格式)、phone(验证手机号格式)、url(验证url格式)等等。 在使用rules进行表单验证时,需要在相应的表单元素中加入“:rules”属性,并将需要验证的规则以数组方式放在属性值中。例如,对于一个需要验证是否为必填、长度在3到10之间的输入框,可以这样定义: <el-input v-model="inputValue" :rules="[required, { min: 3, max: 10, message: '长度在3到10之间' }]" /> 在这个例子中,输入框的内容必须是必填的,且长度必须在3到10个字符之间。如果用户输入的内容不符合这个规则,那么element组件库会自动弹出相应的提示信息,告诉用户输入内容不符合要求。 综上所述,element表单验证规则rules是一种非常实用的前端开发技巧,它可以有效地提高表单输入数据的准确性和完整性,对于web开发中表单的处理非常有帮助。 ### 回答3: Element是一个Vue.js UI库,其中包括了表单组件。为了确保用户提交的数据符合要求,Element提供了表单验证规则rules)来验证表单组件的数据是否符合设定的规则。 在Element中使用表单验证规则,需要在对应的表单组件中设置rules,以确保用户填的数据符合规定。rules可以设置为一个数组或者一个函数,数组中可以包含一系列验证规则选项,函数则是用于更精细的验证场景。 下面是Element中常用的表单验证规则: 1. required:必填项,当字段为空时将会提示用户填。 2. pattern:正则表达式验证,可以使用常见的正则表达式验证格式(如邮箱、手机号、身份证号等)。 3. min/max:限制数字或字符最小/最大值,min/max分别表示数字或字符的最小值和最大值。例如,限制数字输入框中最小值为10,可以设置为min:10。 4. validator:自定义验证规则,可以进行更精细的验证。例如,验证两个输入框中的值是否相等,可以通过一个函数来实现。 5. type:输入类型验证,可以验证输入的数据类型是否符合要求,如可以设置type为email,验证输入的数据是否为正确的电子邮件格式。 需要注意的是,当表单验证规则设置为数组时,会按照数组中的验证规则顺序逐一执行,直到其中某个验证规则失败,则停止后续验证。而当设置为函数时,可以实现更加灵活的验证。 在Element中,通过rules来验证表单的数据是否符合要求,提高了表单数据的准确性和可靠性,方便开发人员编验证逻辑和用户填表单。同时,Element也提供了丰富的表单验证规则选项,可以满足多种验证场景的需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值