aboutsummaryrefslogtreecommitdiff
path: root/build2/cli/init.cxx
blob: c68a62ff47afd45961f5b8f0d162f21eaa1a5977 (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
380
// file      : build2/cli/init.cxx -*- C++ -*-
// license   : MIT; see accompanying LICENSE file

#include <build2/cli/init.hxx>

#include <libbuild2/scope.hxx>
#include <libbuild2/target.hxx>
#include <libbuild2/variable.hxx>
#include <libbuild2/diagnostics.hxx>

#include <libbuild2/config/utility.hxx>

#include <libbuild2/cxx/target.hxx>

#include <build2/cli/target.hxx>
#include <build2/cli/rule.hxx>

using namespace std;
using namespace butl;

namespace build2
{
  namespace cli
  {
    static const compile_rule compile_rule_;

    bool
    config_init (scope& rs,
                 scope& bs,
                 const location& l,
                 bool first,
                 bool optional,
                 module_init_extra&)
    {
      tracer trace ("cli::config_init");
      l5 ([&]{trace << "for " << bs;});

      // Enter variables.
      //
      if (first)
      {
        auto& vp (rs.var_pool ());

        // The special config.cli=false value is recognized as an explicit
        // request to leave the module unconfigured.
        //
        vp.insert<path>    ("config.cli");
        vp.insert<strings> ("config.cli.options");

        //@@ TODO: split version into componets (it is stdver).
        //
        vp.insert<process_path> ("cli.path");
        vp.insert<string>       ("cli.version");
        vp.insert<string>       ("cli.checksum");
        vp.insert<strings>      ("cli.options");
      }

      // Configuration.
      //
      // The plan is as follows: try to configure the module. If this fails,
      // we are using default values, and the module is optional, leave it
      // unconfigured.
      //
      using config::lookup_config;
      using config::specified_config;


      // First take care of the explicit request by the user to leave the
      // module unconfigured.
      //
      bool conf (true);

      if (const path* p = cast_null<path> (rs["config.cli"]))
      {
        conf = p->string () != "false";

        if (!conf && !optional)
          fail (l) << "non-optional module requested to be left unconfigured";
      }

      if (conf)
      {
        // Otherwise we will only honor optional if the user didn't specify
        // any cli configuration explicitly.
        //
        optional = optional && !specified_config (rs, "cli");

        // If the configuration says we are unconfigured, then we should't
        // re-run tests, etc. But we may still need to print the config
        // report.
        //
        conf = !optional || !config::unconfigured (rs, "cli");
      }

      if (first)
      {
        // config.cli
        //
        process_path pp;

        // Return version or empty string if the cli executable is not found
        // or is not the command line interface compiler.
        //
        // @@ This needs some more thinking/cleanup. Specifically, what does
        //    it mean "cli not found"? Is it just not found in PATH? That plus
        //    was not able to execute (e.g., some shared libraries missing)?
        //    That plus cli that we found is something else?
        //
        auto test = [optional, &pp] (const path& cli) -> string
        {
          const char* args[] = {cli.string ().c_str (), "--version", nullptr};

          // @@ TODO: redo using run_start()/run_finish() or even
          //    run<string>(). We have the ability to ignore exit code and
          //    redirect STDERR to STDOUT.

          try
          {
            // Only search in PATH (specifically, omitting the current
            // executable's directory on Windows).
            //
            pp = process::path_search (cli,
                                       true        /* init */,
                                       dir_path () /* fallback */,
                                       true        /* path_only */);
            args[0] = pp.recall_string ();

            if (verb >= 3)
              print_process (args);

            process pr (pp, args, 0, -1); // Open pipe to stdout.

            try
            {
              ifdstream is (move (pr.in_ofd), fdstream_mode::skip);

              // The version should be the last word on the first line. But
              // also check the prefix since there are other things called
              // 'cli', for example, "Mono JIT compiler".
              //
              string v;
              getline (is, v);

              if (v.compare (0, 37,
                             "CLI (command line interface compiler)") == 0)
              {
                size_t p (v.rfind (' '));

                if (p == string::npos)
                  fail << "unexpected output from " << cli;

                v.erase (0, p + 1);
              }
              else
              {
                if (!optional)
                  fail << cli << " is not command line interface compiler" <<
                    info << "use config.cli to override";

                v.clear ();
              }

              is.close (); // Don't block the other end.

              if (pr.wait ())
                return v;

              // Presumably issued diagnostics. Fall through.
            }
            catch (const io_error&)
            {
              pr.wait ();

              // Fall through.
            }

            // Fall through.
          }
          catch (const process_error& e)
          {
            // In some cases this is not enough (e.g., the runtime linker
            // will print scary errors if some shared libraries are not
            // found). So it would be good to redirect child's STDERR.
            //
            if (!optional)
              error << "unable to execute " << args[0] << ": " << e <<
                info << "use config.cli to override";

            if (e.child)
              exit (1);

            // Fall through.
          }

          return string (); // Not found.
        };

        // Adjust module priority (code generator).
        //
        config::save_module (rs, "cli", 150);

        string ver;            // Empty means unconfigured.
        path cli ("cli");      // Default value.
        bool new_cfg (false);  // New configuration.

        if (optional)
        {
          // Test the default value before setting any config.cli.* values
          // so that if we fail to configure, nothing will be written to
          // config.build.
          //
          if (conf)
          {
            ver = test (cli);

            if (ver.empty ())
            {
              conf = false;
              new_cfg = true;
            }
            else
            {
              auto l (lookup_config (new_cfg, rs, "config.cli", cli));
              assert (new_cfg && cast<path> (l) == cli);
            }
          }
        }
        else
        {
          cli = cast<path> (lookup_config (new_cfg, rs, "config.cli", cli));
          ver = test (cli);

          if (ver.empty ())
            throw failed (); // Diagnostics already issued.
        }

        string checksum;
        if (conf)
        {
          // Hash the compiler path and version.
          //
          sha256 cs;
          cs.append (pp.effect_string ());
          cs.append (ver);
          checksum = cs.string ();
        }
        else
        {
          // Note that we are unconfigured so that we don't keep re-testing
          // this on each run.
          //
          new_cfg = config::unconfigured (rs, "cli", true) || new_cfg;
        }

        // If this is a configuration with new values, then print the report
        // at verbosity level 2 and up (-v).
        //
        if (verb >= (new_cfg ? 2 : 3))
        {
          diag_record dr (text);
          dr << "cli " << project (rs) << '@' << rs << '\n';

          if (conf)
            dr << "  cli        " << pp << '\n'
               << "  version    " << ver << '\n'
               << "  checksum   " << checksum;
          else
            dr << "  cli        " << "not found, leaving unconfigured";
        }

        if (conf)
        {
          rs.assign ("cli.path")     = move (pp);
          rs.assign ("cli.version")  = move (ver);
          rs.assign ("cli.checksum") = move (checksum);
        }
      }

      if (conf)
      {
        // config.cli.options
        //
        // This one is optional. We also merge it into the corresponding cli.*
        // variables. See the cc module for more information on this merging
        // semantics and some of its tricky aspects.
        //
        bs.assign ("cli.options") += cast_null<strings> (
          lookup_config (rs, "config.cli.options", nullptr));
      }

      return conf;
    }

    bool
    init (scope& rs,
          scope& bs,
          const location& l,
          bool first,
          bool optional,
          module_init_extra& extra)
    {
      tracer trace ("cli::init");
      l5 ([&]{trace << "for " << bs;});

      // Make sure the cxx module has been loaded since we need its targets
      // types (?xx{}). Note that we don't try to load it ourselves because of
      // the non-trivial variable merging semantics. So it is better to let
      // the user load cxx explicitly.
      //
      if (!cast_false<bool> (bs["cxx.loaded"]))
        fail (l) << "cxx module must be loaded before cli";

      // Load cli.config.
      //
      if (!cast_false<bool> (bs["cli.config.loaded"]))
      {
        if (!init_module (rs, bs, "cli.config", l, optional, extra.hints))
          return false;
      }
      else if (!cast_false<bool> (bs["cli.config.configured"]))
      {
        if (!optional)
          fail (l) << "cli module could not be configured" <<
            info << "re-run with -V for more information";

        return false;
      }

      // Register target types.
      //
      if (first)
      {
        rs.insert_target_type<cli> ();
        rs.insert_target_type<cli_cxx> ();
      }

      // Register our rules.
      //
      {
        auto reg = [&bs] (meta_operation_id mid, operation_id oid)
        {
          bs.insert_rule<cli_cxx>  (mid, oid, "cli.compile", compile_rule_);
          bs.insert_rule<cxx::hxx> (mid, oid, "cli.compile", compile_rule_);
          bs.insert_rule<cxx::cxx> (mid, oid, "cli.compile", compile_rule_);
          bs.insert_rule<cxx::ixx> (mid, oid, "cli.compile", compile_rule_);
        };

        reg (perform_id, update_id);
        reg (perform_id, clean_id);

        // Other rules (e.g., cc::compile) may need to have the group members
        // resolved/linked up. Looks like a general pattern: groups should
        // resolve on *(update).
        //
        // @@ meta-op wildcard?
        //
        reg (configure_id, update_id);
        reg (dist_id, update_id);
      }

      return true;
    }

    static const module_functions mod_functions[] =
    {
      // NOTE: don't forget to also update the documentation in init.hxx if
      //       changing anything here.

      {"cli.config", nullptr, config_init},
      {"cli",        nullptr, init},
      {nullptr,      nullptr, nullptr}
    };

    const module_functions*
    build2_cli_load ()
    {
      return mod_functions;
    }
  }
}