aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/cc/predefs-rule.cxx
blob: e74192d6b0e7bc3ff05b447cf6829f8b65d67b27 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
// file      : libbuild2/cc/predefs-rule.cxx -*- C++ -*-
// license   : MIT; see accompanying LICENSE file

#include <libbuild2/cc/predefs-rule.hxx>

#include <libbuild2/depdb.hxx>
#include <libbuild2/target.hxx>
#include <libbuild2/context.hxx>
#include <libbuild2/algorithm.hxx>
#include <libbuild2/filesystem.hxx>
#include <libbuild2/diagnostics.hxx>

namespace build2
{
  namespace cc
  {
    predefs_rule::
    predefs_rule (data&& d)
        : common (move (d)),
          rule_name (string (x) += ".predefs"),
          rule_id (rule_name + " 1")
    {
    }

    bool predefs_rule::
    match (action, target&, const string& hint, match_extra&) const
    {
      tracer trace (x, "predefs_rule::match");

      // We only match with an explicit hint (failed that, we will turn every
      // header into predefs).
      //
      if (hint == rule_name)
      {
        // Don't match if unsupported compiler. In particular, this allows the
        // user to provide a fallback rule.
        //
        switch (cclass)
        {
        case compiler_class::gcc: return true;
        case compiler_class::msvc:
          {
            // Only MSVC 19.20 or later. Not tested with clang-cl.
            //
            if (cvariant.empty () && (cmaj > 19 || (cmaj == 19 && cmin >= 20)))
              return true;

            l4 ([&]{trace << "unsupported compiler/version";});
            break;
          }
        }
      }

      return false;
    }

    recipe predefs_rule::
    apply (action a, target& xt, match_extra&) const
    {
      file& t (xt.as<file> ());
      t.derive_path ();

      // Inject dependency on the output directory.
      //
      inject_fsdir (a, t);

      if (a == perform_update_id)
      {
        return [this] (action a, const target& xt)
        {
          return perform_update (a, xt);
        };
      }
      else if (a == perform_clean_id)
      {
        return [] (action a, const target& t)
        {
          // Also remove the temporary input source file in case it wasn't
          // removed at the end of the update.
          //
          return perform_clean_extra (a, t.as<file> (), {".d", ".t"});
        };
      }
      else
        return noop_recipe; // Configure update.
    }

    // Filter noise, sanitize options (msvc.cxx).
    //
    void
    msvc_filter_cl (diag_buffer&, const path& src);

    void
    msvc_sanitize_cl (cstrings&);

    target_state predefs_rule::
    perform_update (action a, const target& xt) const
    {
      tracer trace (x, "predefs_rule::perform_update");

      const file& t (xt.as<file> ());
      const path& tp (t.path ());

      context& ctx (t.ctx);

      const scope& rs (t.root_scope ());

      // Execute prerequisites (the output directory being the only one thus
      // not mtime checking).
      //
      execute_prerequisites (a, t);

      // Use depdb to track changes to options, compiler, etc (similar to
      // the compile_rule).
      //
      depdb dd (tp + ".d");
      {
        // First should come the rule name/version.
        //
        if (dd.expect (rule_id) != nullptr)
          l4 ([&]{trace << "rule mismatch forcing update of " << t;});

        // Then the compiler checksum.
        //
        if (dd.expect (cast<string> (rs[x_checksum])) != nullptr)
          l4 ([&]{trace << "compiler mismatch forcing update of " << t;});

        // Then the compiler environment checksum.
        //
        if (dd.expect (env_checksum) != nullptr)
          l4 ([&]{trace << "environment mismatch forcing update of " << t;});

        // Finally the options checksum (as below).
        //
        {
          sha256 cs;
          append_options (cs, t, c_coptions);
          append_options (cs, t, x_coptions);
          append_options (cs, cmode);

          if (dd.expect (cs.string ()) != nullptr)
            l4 ([&]{trace << "options mismatch forcing update of " << t;});
        }
      }

      // Update if depdb mismatch.
      //
      bool update (dd.writing () || dd.mtime > t.load_mtime ());

      dd.close ();

      if (!update)
        return target_state::unchanged; // No mtime-based prerequisites.

      // Prepare the compiler command-line.
      //
      cstrings args {cpath.recall_string ()};

      // Append compile options.
      //
      // Note that any command line macros that we specify with -D will end up
      // in the predefs, which is something we don't want. So no poptions.
      //
      append_options (args, t, c_coptions);
      append_options (args, t, x_coptions);
      append_options (args, cmode);

      // The output and input paths, relative to the working directory for
      // easier to read diagnostics.
      //
      path relo (relative (tp));
      path reli;

      // Add compiler-specific command-line arguments.
      //
      switch (cclass)
      {
      case compiler_class::gcc:
        {
          // Add implied options which may affect predefs, similar to the
          // compile rule.
          //
          if (!find_option_prefix ("-finput-charset=", args))
            args.push_back ("-finput-charset=UTF-8");

          if (ctype == compiler_type::clang && tsys == "win32-msvc")
          {
            if (!find_options ({"-nostdlib", "-nostartfiles"}, args))
            {
              args.push_back ("-D_MT");
              args.push_back ("-D_DLL");
            }
          }

          if (ctype == compiler_type::clang && cvariant == "emscripten")
          {
            if (x_lang == lang::cxx)
            {
              if (!find_option_prefix ("DISABLE_EXCEPTION_CATCHING=", args))
              {
                args.push_back ("-s");
                args.push_back ("DISABLE_EXCEPTION_CATCHING=0");
              }
            }
          }

          args.push_back ("-E");  // Stop after the preprocessing stage.
          args.push_back ("-dM"); // Generate #define directives.

          // Output.
          //
          args.push_back ("-o");
          args.push_back (relo.string ().c_str ());

          // Input.
          //
          args.push_back ("-x");
          switch (x_lang)
          {
          case lang::c:   args.push_back ("c"); break;
          case lang::cxx: args.push_back ("c++"); break;
          }

          // With GCC and Clang we can compile /dev/null as stdin by
          // specifying `-` and thus omitting the temporary file.
          //
          args.push_back ("-");

          break;
        }
      case compiler_class::msvc:
        {
          // Add implied options which may affect predefs, similar to the
          // compile rule.
          //
          {
            // Note: these affect the _MSVC_EXECUTION_CHARACTER_SET, _UTF8
            // macros.
            //
            bool sc (find_option_prefixes (
                       {"/source-charset:", "-source-charset:"}, args));
            bool ec (find_option_prefixes (
                       {"/execution-charset:", "-execution-charset:"}, args));

            if (!sc && !ec)
              args.push_back ("/utf-8");
            else
            {
              if (!sc)
                args.push_back ("/source-charset:UTF-8");

              if (!ec)
                args.push_back ("/execution-charset:UTF-8");
            }
          }

          if (x_lang == lang::cxx)
          {
            if (!find_option_prefixes ({"/EH", "-EH"}, args))
              args.push_back ("/EHsc");
          }

          if (!find_option_prefixes ({"/MD", "/MT", "-MD", "-MT"}, args))
            args.push_back ("/MD");

          msvc_sanitize_cl (args);

          args.push_back ("/nologo");

          // /EP may seem like it contradicts /P but it's the recommended
          // way to suppress `#line`s from the output of the /P option (see
          // /P in the "MSVC Compiler Options" documentation).
          //
          args.push_back ("/P");  // Write preprocessor output to a file.
          args.push_back ("/EP"); // Preprocess to stdout without `#line`s.

          args.push_back ("/PD");              // Print all macro definitions.
          args.push_back ("/Zc:preprocessor"); // Preproc. conformance mode.

          // Output (note that while the /Fi: variant is only availbale
          // starting with VS2013, /Zc:preprocessor is only available in
          // starting from VS2019).
          //
          args.push_back ("/Fi:");
          args.push_back (relo.string ().c_str ());

          // Input.
          //
          switch (x_lang)
          {
          case lang::c:   args.push_back ("/TC"); break;
          case lang::cxx: args.push_back ("/TP"); break;
          }

          // Input path.
          //
          // Note that with MSVC we have to use a temporary file. In
          // particular compiling `nul` does not work.
          //
          reli = relo + ".t";
          args.push_back (reli.string ().c_str ());

          break;
        }
      }

      args.push_back (nullptr);

      // Run the compiler.
      //
      if (verb >= 2)
        print_process (args);
      else if (verb)
        print_diag ((string (x_name) + "-predefs").c_str (), t);

      if (!ctx.dry_run)
      {
        // Create an empty temporary input source file, if necessary.
        //
        auto_rmfile rmi;
        if (!reli.empty ())
        {
          rmi = auto_rmfile (reli);

          if (exists (reli, false /* follow_symlinks */))
            rmfile (ctx, reli, 3 /* verbosity */);

          touch (ctx, reli, true /* create */, 3 /* verbosity */);
        }

        try
        {
          // VC cl.exe sends diagnostics to stdout. It also prints the file
          // name being compiled as the first line. So for cl.exe we filter
          // that noise out.
          //
          // For other compilers also redirect stdout to stderr, in case any
          // of them tries to pull off something similar. For sane compilers
          // this should be harmless.
          //
          // We also redirect stdin to /dev/null in case that's used instead
          // of the temporary file.
          //
          // Note: similar logic as in compile_rule.
          //
          bool filter (ctype == compiler_type::msvc);

          process pr (cpath,
                      args,
                      -2,                                         /* stdin  */
                      2,                                          /* stdout */
                      diag_buffer::pipe (ctx, filter /* force */) /* stderr */);

          diag_buffer dbuf (ctx, args[0], pr);

          if (filter)
            msvc_filter_cl (dbuf, reli);

          dbuf.read ();

          run_finish (dbuf, args, pr, 1 /* verbosity */);
          dd.check_mtime (tp);
        }
        catch (const process_error& e)
        {
          error << "unable to execute " << args[0] << ": " << e;

          if (e.child)
            exit (1);

          throw failed ();
        }
      }

      t.mtime (system_clock::now ());
      return target_state::changed;
    }
  }
}