make项目的源码阅读

发布时间 2023-05-28 22:24:59作者: zwlwf

这里下载https://ftp.gnu.org/gnu/make/make-4.4.tar.gz进行研读。

目标

研读的初始目的,是想看看make打印的构建命令在哪执行的。

构建make

make项目本身的编译,可以用./configure && make来完成。

代码挖呀挖

make中target抽象为struct file,并用链来组织,

struct file
  {
    const char *name;
    const char *hname;          /* Hashed filename */
    const char *vpath;          /* VPATH/vpath pathname */
    struct dep *deps;           /* all dependencies, including duplicates */
    struct commands *cmds;      /* Commands to execute for this target.  */
    const char *stem;           /* Implicit stem, if an implicit
                                   rule has been used */
    struct dep *also_make;      /* Targets that are made by making this.  */
    struct file *prev;          /* Previous entry for same file name;
                                   used when there are multiple double-colon
                                   entries for the same file.  */
    struct file *last;          /* Last entry for the same file name.  */

    /* File that this file was renamed to.  After any time that a
       file could be renamed, call 'check_renamed' (below).  */
    struct file *renamed;

    /* List of variable sets used for this file.  */
    struct variable_set_list *variables;

    /* Pattern-specific variable reference for this target, or null if there
       isn't one.  Also see the pat_searched flag, below.  */
    struct variable_set_list *pat_variables;

    /* Immediate dependent that caused this target to be remade,
       or nil if there isn't one.  */
    struct file *parent;

    /* For a double-colon entry, this is the first double-colon entry for
       the same file.  Otherwise this is null.  */
    struct file *double_colon;

    FILE_TIMESTAMP last_mtime;  /* File's modtime, if already known.  */
    FILE_TIMESTAMP mtime_before_update; /* File's modtime before any updating
                                           has been performed.  */
    unsigned int considered;    /* equal to 'considered' if file has been
                                   considered on current scan of goal chain */
    int command_flags;          /* Flags OR'd in for cmds; see commands.h.  */
    enum update_status          /* Status of the last attempt to update.  */
      {
        us_success = 0,         /* Successfully updated.  Must be 0!  */
        us_none,                /* No attempt to update has been made.  */
        us_question,            /* Needs to be updated (-q is is set).  */
        us_failed               /* Update failed.  */
      } update_status ENUM_BITFIELD (2);
    enum cmd_state              /* State of commands.  ORDER IS IMPORTANT!  */
      {
        cs_not_started = 0,     /* Not yet started.  Must be 0!  */
        cs_deps_running,        /* Dep commands running.  */
        cs_running,             /* Commands running.  */
        cs_finished             /* Commands finished.  */
      } command_state ENUM_BITFIELD (2);

    unsigned int builtin:1;     /* True if the file is a builtin rule. */
    unsigned int precious:1;    /* Non-0 means don't delete file on quit */
    unsigned int loaded:1;      /* True if the file is a loaded object. */
    unsigned int unloaded:1;    /* True if this loaded object was unloaded. */
    unsigned int low_resolution_time:1; /* Nonzero if this file's time stamp
                                           has only one-second resolution.  */
    unsigned int tried_implicit:1; /* Nonzero if have searched
                                      for implicit rule for making
                                      this file; don't search again.  */
    unsigned int updating:1;    /* Nonzero while updating deps of this file */
    unsigned int updated:1;     /* Nonzero if this file has been remade.  */
    unsigned int is_target:1;   /* Nonzero if file is described as target.  */
    unsigned int cmd_target:1;  /* Nonzero if file was given on cmd line.  */
    unsigned int phony:1;       /* Nonzero if this is a phony file
                                   i.e., a prerequisite of .PHONY.  */
    unsigned int intermediate:1;/* Nonzero if this is an intermediate file.  */
    unsigned int is_explicit:1; /* Nonzero if explicitly mentioned. */
    unsigned int secondary:1;   /* Nonzero means remove_intermediates should
                                   not delete it.  */
    unsigned int notintermediate:1; /* Nonzero means a file is a prereq to
                                       .NOTINTERMEDIATE.  */
    unsigned int dontcare:1;    /* Nonzero if no complaint is to be made if
                                   this target cannot be remade.  */
    unsigned int ignore_vpath:1;/* Nonzero if we threw out VPATH name.  */
    unsigned int pat_searched:1;/* Nonzero if we already searched for
                                   pattern-specific variables.  */
    unsigned int no_diag:1;     /* True if the file failed to update and no
                                   diagnostics has been issued (dontcare). */
    unsigned int was_shuffled:1; /* Did we already shuffle 'deps'? used when
                                    --shuffle passes through the graph.  */
    unsigned int snapped:1;     /* True if the deps of this file have been
                                   secondary expanded.  */
  };

update_file
update_file_1 两个函数递归调用来完成target更新。

最终当检测到target的依赖有更新的话,就调用的是remake_file函数,来真实执行命令。
remake_file函数如下,

static void
remake_file (struct file *file)
{
  if (file->cmds == 0)
    {
      if (file->phony)
        /* Phony target.  Pretend it succeeded.  */
        file->update_status = us_success;
      else if (file->is_target)
        /* This is a nonexistent target file we cannot make.
           Pretend it was successfully remade.  */
        file->update_status = us_success;
      else
        {
          /* This is a dependency file we cannot remake.  Fail.  */
          if (!rebuilding_makefiles || !file->dontcare)
            complain (file);
          file->update_status = us_failed;
        }
    }
  else
    {
      chop_commands (file->cmds); //将命令切分成多条

      /* The normal case: start some commands.  */
      if (!touch_flag || file->cmds->any_recurse)
        {
          execute_file_commands (file);
          return;
        }

      /* This tells notice_finished_file it is ok to touch the file.  */
      file->update_status = us_success;
    }

  /* This does the touching under -t.  */
  notice_finished_file (file);
}
struct commands
  {
    floc fileinfo;              /* Where commands were defined.  */
    char *commands;             /* Commands text.  */
    char **command_lines;       /* Commands chopped up into lines.  */
    unsigned char *lines_flags; /* One set of flag bits for each line.  */
    unsigned short ncommand_lines;/* Number of command lines.  */
    char recipe_prefix;         /* Recipe prefix for this command set.  */
    unsigned int any_recurse:1; /* Nonzero if any 'lines_flags' elt has */
                                /* the COMMANDS_RECURSE bit set.  */
  };

最后找到打印命令在src/job.cstart_job_cpmmand 的1359行,

  /* Print the command if appropriate.  */
  if (just_print_flag || ISDB (DB_PRINT)
      || (!(flags & COMMANDS_SILENT) && !run_silent))
    OS (message, 0, "%s\n", p);

看代码的过程学到一些实用的技巧,

  1. make -n不编译,只打印编译的构建过程,而不真正去编译。这个可以用来收集编译过程。执行过程中,被执行的命令往往都有

  2. make命令中每个target下面多行命令默认是放在不同shell中执行的,如下a.out的target是三条名字,第一条对第二条不影响,所以ls得到的文件列表不是home目录下的。有一种one_shellmode可以将同一个target下的所有命令用,详情参看https://www.gnu.org/software/make/manual/html_node/One-Shell.html.

    a.out: a.c
    	cd ~
    	ls
    	gcc a.c