add .merlin file.
[tatoo.git] / remake.cpp
index b8387be..b36a3e7 100644 (file)
@@ -184,6 +184,10 @@ parser.c parser.h: parser.y
        yacc -d -o parser.c parser.y
 @endverbatim
 
+\subsection sec-special-tgt Special targets
+
+Target <tt>.PHONY</tt> marks its prerequisites as being always obsolete.
+
 \subsection sec-special-var Special variables
 
 Variable <tt>.OPTIONS</tt> is handled specially. Its content enables some
@@ -343,7 +347,7 @@ https://github.com/apenwarr/redo for an implementation and some comprehensive do
 \section sec-licensing Licensing
 
 @author Guillaume Melquiond
-@version 0.9
+@version 0.11
 @date 2012-2013
 @copyright
 This program is free software: you can redistribute it and/or modify
@@ -373,8 +377,8 @@ When building a target, the following sequence of events happens:
 
 - #start calls #find_rule (and #find_generic_rule) to get the rule.
 - It then creates a pseudo-client if the rule has static dependencies, or
-  calls #run_script otherwise. In both cases, a new job is created and its
-  targets are put into #job_targets.
+  calls #run_script otherwise. In both cases, a new job is created; the
+  rule and the variables are stored into #jobs.
 - #run_script creates a shell process and stores it in #job_pids. It
   increases #running_jobs.
 - The child process possibly calls <b>remake</b> with a list of targets.
@@ -392,7 +396,7 @@ When building a target, the following sequence of events happens:
 - When a child process ends, #server_loop calls #finalize_job, which
   removes the process from #job_pids, decreases #running_jobs, and calls
   #complete_job.
-- #complete_job removes the job from #job_targets and calls #update_status
+- #complete_job removes the job from #jobs and calls #update_status
   to change the status of the targets. It also removes the target files in
   case of failure.
 */
@@ -668,7 +672,7 @@ static int waiting_jobs = 0;
 
 /**
  * Global counter used to produce increasing job numbers.
- * @see job_targets
+ * @see jobs
  */
 static int job_counter = 0;
 
@@ -719,6 +723,11 @@ static std::string working_dir;
  */
 static std::string prefix_dir;
 
+/**
+ * Whether the prefix directory is different from #working_dir.
+ */
+static bool changed_prefix_dir;
+
 /**
  * Whether target-specific variables are propagated to prerequisites.
  */
@@ -870,7 +879,16 @@ static void init_prefix_dir()
                struct stat s;
                if (stat((prefix_dir + "/Remakefile").c_str(), &s) == 0)
                {
-                       chdir(prefix_dir.c_str());
+                       if (!changed_prefix_dir) return;
+                       if (chdir(prefix_dir.c_str()))
+                       {
+                               perror("Failed to change working directory");
+                               exit(EXIT_FAILURE);
+                       }
+                       if (show_targets)
+                       {
+                               std::cout << "remake: Entering directory `" << prefix_dir << '\'' << std::endl;
+                       }
                        return;
                }
                size_t pos = prefix_dir.find_last_of('/');
@@ -880,6 +898,7 @@ static void init_prefix_dir()
                        exit(EXIT_FAILURE);
                }
                prefix_dir.erase(pos);
+               changed_prefix_dir = true;
        }
 }
 
@@ -1076,44 +1095,44 @@ static int expect_token(std::istream &in, int mask)
 /**
  * Read a (possibly quoted) word.
  */
-static std::string read_word(std::istream &in)
+static std::string read_word(std::istream &in, bool detect_equal = true)
 {
-       int c = in.get();
+       int c = in.peek();
        std::string res;
        if (!in.good()) return res;
-       char const *separators = " \t\r\n:$(),=+\"";
+       char const *separators = " \t\r\n$(),:";
        bool quoted = c == '"';
-       if (!quoted)
-       {
-               if (strchr(separators, c))
-               {
-                       in.putback(c);
-                       return res;
-               }
-               res += c;
-       }
+       if (quoted) in.ignore(1);
+       bool plus = false;
        while (true)
        {
-               c = in.get();
+               c = in.peek();
                if (!in.good()) return res;
                if (quoted)
                {
+                       in.ignore(1);
                        if (c == '\\')
                                res += in.get();
                        else if (c == '"')
-                               return res;
+                               quoted = false;
                        else
                                res += c;
+                       continue;
                }
-               else
+               if (detect_equal && c == '=')
                {
-                       if (strchr(separators, c))
-                       {
-                               in.putback(c);
-                               return res;
-                       }
-                       res += c;
+                       if (plus) in.putback('+');
+                       return res;
                }
+               if (plus)
+               {
+                       res += '+';
+                       plus = false;
+               }
+               if (strchr(separators, c)) return res;
+               in.ignore(1);
+               if (detect_equal && c == '+') plus = true;
+               else res += c;
        }
 }
 
@@ -1218,11 +1237,11 @@ input_status input_generator::next(std::string &res)
        switch (expect_token(in, Word | Dollarpar))
        {
        case Word:
-               res = read_word(in);
+               res = read_word(in, false);
                return Success;
        case Dollarpar:
        {
-               std::string name = read_word(in);
+               std::string name = read_word(in, false);
                if (name.empty()) return SyntaxError;
                if (expect_token(in, Rightpar))
                        nested = new variable_generator(name, local_variables);
@@ -1648,6 +1667,17 @@ static void load_rule(std::istream &in, std::string const &first)
        }
        rule.script = buf.str();
 
+       // Register phony targets.
+       if (rule.targets.front() == ".PHONY")
+       {
+               for (string_list::const_iterator i = rule.deps.begin(),
+                    i_end = rule.deps.end(); i != i_end; ++i)
+               {
+                       status[*i].status = Todo;
+               }
+               return;
+       }
+
        // Add generic rules to the correct set.
        if (generic)
        {
@@ -2021,21 +2051,23 @@ static bool still_need_rebuild(std::string const &target)
  */
 static void complete_job(int job_id, bool success)
 {
-       DEBUG_open << "Completing job " << job_id << "... ";
+       DEBUG << "Completing job " << job_id << '\n';
        job_map::iterator i = jobs.find(job_id);
        assert(i != jobs.end());
        string_list const &targets = i->second.rule.targets;
        if (success)
        {
+               if (show_targets) std::cout << "Finished";
                for (string_list::const_iterator j = targets.begin(),
                     j_end = targets.end(); j != j_end; ++j)
                {
                        update_status(*j);
+                       if (show_targets) std::cout << ' ' << *j;
                }
+               if (show_targets) std::cout << std::endl;
        }
        else
        {
-               DEBUG_close << "failed\n";
                std::cerr << "Failed to build";
                for (string_list::const_iterator j = targets.begin(),
                     j_end = targets.end(); j != j_end; ++j)
@@ -2137,25 +2169,17 @@ static std::string prepare_script(job_t const &job)
  */
 static status_e run_script(int job_id, job_t const &job)
 {
-       if (show_targets)
-       {
-               std::cout << "Building";
-               for (string_list::const_iterator i = job.rule.targets.begin(),
-                    i_end = job.rule.targets.end(); i != i_end; ++i)
-               {
-                       std::cout << ' ' << *i;
-               }
-               std::cout << std::endl;
-       }
-
        ref_ptr<dependency_t> dep;
        dep->targets = job.rule.targets;
        dep->deps.insert(job.rule.deps.begin(), job.rule.deps.end());
+       if (show_targets) std::cout << "Building";
        for (string_list::const_iterator i = job.rule.targets.begin(),
             i_end = job.rule.targets.end(); i != i_end; ++i)
        {
                dependencies[*i] = dep;
+               if (show_targets) std::cout << ' ' << *i;
        }
+       if (show_targets) std::cout << std::endl;
 
        std::string script = prepare_script(job);
 
@@ -2792,6 +2816,10 @@ static void server_mode(std::string const &remakefile, string_list const &target
        free(socket_name);
 #endif
        save_dependencies();
+       if (show_targets && changed_prefix_dir)
+       {
+               std::cout << "remake: Leaving directory `" << prefix_dir << '\'' << std::endl;
+       }
        exit(build_failure ? EXIT_FAILURE : EXIT_SUCCESS);
 }
 
@@ -2927,8 +2955,6 @@ static void usage(int exit_status)
  */
 int main(int argc, char *argv[])
 {
-       init_working_dir();
-
        std::string remakefile;
        string_list targets;
        bool literal_targets = false;
@@ -2973,11 +2999,14 @@ int main(int argc, char *argv[])
                                continue;
                        }
                        new_target:
-                       targets.push_back(normalize(arg, working_dir, working_dir));
+                       targets.push_back(arg);
                        DEBUG << "New target: " << arg << '\n';
                }
        }
 
+       init_working_dir();
+       normalize_list(targets, working_dir, working_dir);
+
        if (indirect_targets)
        {
                load_dependencies(std::cin);