注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

Tsecer的回音岛

Tsecer的博客

 
 
 

日志

 
 

make中注释处理及逻辑换行  

2013-02-24 12:23:02|  分类: make源代码分析 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
一、注释
注释是每种语言中必须有的机制,否则写出来的代码无法维护,另外不让程序员写注释也就相当于只让吃饭不让吃肉一样。但是unix系统下大部分脚本文件中的注释都是使用'#'这个符号来表示的,这一点是被内核承认的。内核中内置支持的脚本文件就是文件的最开始使用的是"#!"表示是一个脚本文件。这样的机制固然有一种统一的效果,就是所有的脚本文件大家心照不宣的使用同一个注释符号,不用猜也不用想,但是对于可能存在文本间嵌套的一些叫本文文件,这些处理方法可能就有些问题。
例如在makefile中,我们可能需要让一个shell工具(例如grep,sed)等处理一个配置文件,此时就需要将makefile的注释符号传递给bash来执行,而这个注释同样也是bash的注释符号,所以如果直接传递给shell,则此时shell可能会把这个内容当做注释来处理,从而之后的语法解析出现错误或者误解。
大家可以考虑这样的一个例子,需要在makefile中读取一个配置文件的配置项,并且忽略这个配置项中的以'#'开头的行,把这些配置项读取到一个makefile的变量中来。这样其实三个文件都是使用#作为注释,这下就有些混乱了。那么有些人可能说,为什么配置文件也是用#表示注释呢,使用其它一个符号不行吗?其实是可以的,但是这样相当于把开发者的问题推卸给了使用者,因为对于使用者来说,#是最为天经地义的使用方法,而此时为了解决语法的问题,使用者需要很意外的发现需要使用一个非常诡异的符号。
二、make对注释和换行的解析实现
makefile对于文件的解析是通过read.c:eval函数来完成的,所以我们看make对于语法的解析主要需要看这个函数
      nlines = readline (ebuf);

      /* If there is nothing left to eval, we're done.  */
      if (nlines < 0)
        break;

      /* If this line is empty, skip it.  */
      line = ebuf->buffer;
      if (line[0] == '\0')
        continue;

      linelen = strlen (line);
 /* Check for a shell command line first.
     If it is not one, we can stop treating tab specially.  */
      if (line[0] == '\t')
    {
      if (no_targets)
        /* Ignore the commands in a rule with no targets.  */
        continue;

      /* If there is no preceding rule line, don't treat this line
         as a command, even though it begins with a tab character.
         SunOS 4 make appears to behave this way.  */

      if (filenames != 0)
        {
          if (ignoring)
        /* Yep, this is a shell command, and we don't care.  */
        continue;

          /* Append this command line to the line being accumulated.  */
          if (commands_idx == 0)
        cmds_started = ebuf->floc.lineno;

          if (linelen + 1 + commands_idx > commands_len)
        {
          commands_len = (linelen + 1 + commands_idx) * 2;
          commands = xrealloc (commands, commands_len);
        }
          bcopy (line, &commands[commands_idx], linelen);
          commands_idx += linelen;
          commands[commands_idx++] = '\n';

          continue;
        }
    }

      if (collapsed_length < linelen+1)
    {
      collapsed_length = linelen+1;
          if (collapsed)
            free ((char *)collapsed);
      collapsed = (char *) xmalloc (collapsed_length);
    }
      strcpy (collapsed, line);
      /* Collapse continuation lines.  */
      collapse_continuations (collapsed);
      remove_comments (collapsed);

      /* Compare a word, both length and contents. */
#define    word1eq(s)     (len == sizeof(s)-1 && strneq (s, p, sizeof(s)-1))
      p = collapsed;
……
      if (try_variable_definition (fstart, p, o_file, 0))
    /* This line has been dealt with.  */
    goto rule_complete;
……
        /* Search the line for an unquoted ; that is not after an
           unquoted #.  */
        cmdleft = find_char_unquote (line, ';', '#', 0, 1);
        if (cmdleft != 0 && *cmdleft == '#')
          {
            /* We found a comment before a semicolon.  */
            *cmdleft = '\0';
            cmdleft = 0;
          }
        else if (cmdleft != 0)
          /* Found one.  Cut the line short there before expanding it.  */
          *(cmdleft++) = '\0';
        semip = cmdleft;

        collapse_continuations (line);

这里需要注意的几个问题是理解make对于语法解析的关键:
1、make的readline函数内置识别逻辑续行,在执行readline时,如果一行是续行符号,那么该函数会一直将所有逻辑续行全部读入到buffer中。
2、对于一个一制表符(‘\t’)开始的文件行,如果之前有make的target还没有结束(结束的条件就是遇到一个target之后遇到另一个非target的指示),则对于该行内容不做任何解释,直接作为makefile中执行动作记录起来,代码中使用的是bcopy。所谓的不做任何解释包括没有对注释进行删除,没有进行逻辑续行(\\n)进行折叠,没有进行变量展开。
3、逻辑续行的折叠在任何语法处理之前,所以逻辑折叠是无条件的,早于任何语法和语义处理。
4、注释的删除remove_comments就在折叠之后,直接从中搜索那些#字符,从前向后搜索,遇到的第一个合法的注释之后所有内容被丢弃。这里的合法注释就是前面没有被转义符转义,这个处理也是在语法分析之前,此时不会识别引号,变量引用的括号等分组结构。
5、在尝试变量定义解析的时候,使用的字符串同样是已经删除了注释的字符串,而注释的删除规则也是在语法识别之前。
6、目标的解析使用的是未删除注释的原始字符串,然后在解析的时候它对于注释的识别更加的只能,开始可以识别语法中的分组信息,因为在调用find_char_unquote (line, ';', '#', 0, 1)时传入的最后一个参数是1,表示变量引用中的注释会被保留。
三、对于开始说的问题的实现
[root@Harry makecomment]# cat makefile
var=$(shell grep -v -e '^[[:space:]]*#' patterns.txt)
default:
    echo $(var)
[root@Harry makecomment]# cat patterns.txt
    #comments
text

[root@Harry makecomment]# make
makefile:1: *** unterminated call to function `shell': missing `)'.  Stop.
[root@Harry makecomment]#
可以看到系统会提示说语法不正确,对于shell命令来说没有匹配的结束符号,因为这个额呢绒虽然在引号中,但是早早的被make给当做注释给删除掉了。
解决的办法也很简单,就是在注释符号前面加上一个转义字符就好了
[root@Harry makecomment]# cat makefile
var=$(shell grep -v -e '^[[:space:]]*\#' patterns.txt)
default:
    echo $(var)
[root@Harry makecomment]# cat patterns.txt
    #comments
text

[root@Harry makecomment]# make
echo text
四、逻辑换行问题
这个在之前已经说过,make命令中的内容会被原封不动的(当然make会对变量进行展开)传递给shell,包括着逻辑换行符。这些换行符的处理由shell来完成,make对此并并不知道,也不关心。
所以对于一些复杂的make规则,可以使用逻辑换行将它们连接起来,当然这些逻辑换行的命令只要第一行是以制表符开始即可,这一点从上面看到的代码可以知道。对于make规则中的注释,它可以写在规则的开始,也可以写在规则的制表符之后。例如
[root@Harry makecomment]# cat conitline
default:
    if [[ -a makefile ]] ; then \
echo exist makefile ; \
fi
    #this is a recipe comment
#thisi is a gloabl comment
    echo finally
[root@Harry makecomment]# ls
conitline  makefile  patterns.txt
[root@Harry makecomment]# make -f conitline
if [[ -a makefile ]] ; then \
echo exist makefile ; \
fi
exist makefile
#this is a recipe comment
echo finally
finally
[root@Harry makecomment]#
可以看到,
1、global注释没有被shell回显,说明这个命令没有传递给shell,虽然它没有以tab开始,但是并不会终止一个recipe。
2、使用逻辑换行符连接的recipe命令,只有第一个需要以tab开始,接下来的是可选的。
3、recipe内部的注释会被make回显,说明make并没有解释这个字段的内容和语法,它只负责变量的展开。所以如果希望在make的输出中添加一些提示性的注释(也就是make的执行者在执行中也可以看到的提示,可以考虑将它们加在recipe的指令中)
  评论这张
 
阅读(1124)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017