aboutsummaryrefslogtreecommitdiff
path: root/build.jai
blob: abec9b12a189308e27474c35e6c4c3d1619d4f61 (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
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
is_absolute_path :: (path: string) -> bool { // This routine is probably not as correct as we'd like. We'd like to put in a better one! But maybe we will stop doing the cwd thing, or do it differently; hard to say.
    if !path return false;

    if path[0] == "/" return true;   // Backslashes have not been converted to forward slashes by this point.
    if path[0] == "\\" return true;  // Backslashes have not been converted to forward slashes by this point.
    if (path.count > 2) && (path[1] == #char ":") && (OS == .WINDOWS) return true;  // Drive letter stuff. Probably incomplete.

    if path.count >= 3 {
        // @Robustness: Check for a drive letter in character 0?  Anything else?
        if path[1] == #char ":" return true;
    }

    return false;
}

build :: () {
    //
    // Create a workspace to contain the program we want to compile.
    // We can pass a name to compiler_create_workspace that gets reported
    // back to us in error messages:
    //
    w := compiler_create_workspace("Target Program");
    if !w {
        log_error("Workspace creation failed.\n");
        return;
    }

    args := get_build_options().compile_time_command_line;

    options := get_build_options(w);

    check_bindings   := true;
    do_check         := true;  // Import modules/Check if true.

    if verbose  print("Arguments:   %\n", args); // @Cleanup: 'verbose' cannot be true yet so this will never fire.

    printed_help    := false;
    printed_version := false;

    // User-processed options will give us entries for either 'files' (files to load)
    // or 'run_strings' (strings to add to the build with #run in front.)
    files:         [..] string;
    add_strings:   [..] string;
    run_strings:   [..] string;
    modules_paths: [..] string;

    user_arguments: [..] string;

    // Handle command-line arguments. Once we see + or -,
    // we are done looking for our arguments.

    got_error       := false;

    output_executable_name: string;
    output_path:            string;

    intercept_flags: Intercept_Flags;

    success, plugins_to_create, remaining_args := parse_plugin_arguments(args);
    if !success got_error = true;

    args = remaining_args;
    index := 0;
    while index < args.count {
        defer index += 1;

        it := args[index];

        if !it continue;  // @Temporary?

        if it[0] == #char "-" {
            if it == {
                case "-";
                    // Everything after this is user arguments.
                    for i: index+1..args.count-1  array_add(*user_arguments, args[i]);
                    break;

                case "-release";
                    log("*** Warning: -release will go away, because it's a confusing term inherited from Microsoft. Please use -o or -optimized instead.\n", flags=.WARNING);
                    set_optimization(*options, .VERY_OPTIMIZED);
                    options.stack_trace = false;
                
                case "-release_debug";
                    log("*** Warning: -release_debug will go away, because 'release' is a confusing term inherited from Microsoft. Please use -od or -optimized_debug instead.\n", flags=.WARNING);
                    set_optimization(*options, .OPTIMIZED);
                    // Leave options.stack_trace == true...
                
                case "-o"; #through;
                case "-optimized";
                    set_optimization(*options, .VERY_OPTIMIZED);
                    options.stack_trace = false;

                case "-od"; #through;
                case "-optimized_debug";
                    set_optimization(*options, .OPTIMIZED);
                     // Leave options.stack_trace == true...
                
                case "-very_debug";
                    set_optimization(*options, .VERY_DEBUG);

                case "-no_inline";
                    options.enable_bytecode_inliner = false;

                case "-quiet";
                    options.text_output_flags = 0;

                case "-x64";
                    options.backend = .X64;
                case "-llvm";
                    options.backend = .LLVM;

                case "-no_dce";
                    options.dead_code_elimination = .NONE;

                case "-no_split";
                    options.llvm_options.enable_split_modules = false;

                case "-output_ir";
                    options.llvm_options.output_llvm_ir_before_optimizations = true;
                    options.llvm_options.output_llvm_ir = true;

                case "-debug_for";
                    options.debug_for_expansions = true;

                case "-msvc_format";
                    options.use_visual_studio_message_format = true;

                case "-natvis";
                    options.use_natvis_compatible_types = true;

                // We need to know about no_check bindings in pass 1 because that is how we determine if that plugin is in the set! Argh.
                case "-no_check";           do_check = false;
                case "-no_check_bindings";  check_bindings = false;
                case "-check_bindings";     check_bindings = true; // Not necessary anymore since we changed the default. I just left it in for backwards compatibility. -rluba, 2022-03-23

                case "-no_backtrace_on_crash";
                    options.backtrace_on_crash = .OFF;

                case "-version";
                    s := compiler_get_version_info(null);
                    print("Version: %.\n", s);
                    printed_version = true;

                case "-exe";
                    if index >= args.count-1 {
                        log_error("Command line: Missing argument to -exe.\n");
                        got_error = true;
                        break;
                    }

                    index += 1;
                    output_executable_name = args[index];

                case "-output_path";
                    if index >= args.count-1 {
                        log_error("Command line: Missing argument to -output_path.\n");
                        got_error = true;
                        break;
                    }

                    index += 1;
                    output_path = args[index];

                case "-add";

                    if index >= args.count-1 {
                        log_error("Command line: Missing argument to -add.\n");
                        got_error = true;
                        break;
                    }

                    index += 1;
                    array_add(*add_strings, args[index]);

                case "-run";

                    if index >= args.count-1 {
                        log_error("Command line: Missing argument to -run.\n");
                        got_error = true;
                        break;
                    }

                    index += 1;
                    array_add(*run_strings, args[index]);

                case "-help"; #through;
                case "-?";
                    log("%", HELP_STRING);

                    printed_help = true;

                case "-debugger";
                    options.interactive_bytecode_debugger = true;
                    set_build_options_dc(.{interactive_bytecode_debugger=true});

                case "-context_size";
                    if index >= args.count-1 {
                        log_error("Command line: Missing argument to -context_size.\n");
                        got_error = true;
                        break;
                    }

                    index += 1;

                    value, success := to_integer(args[index]);
                    if success {
                        CONTEXT_SIZE_MAX :: 0x4_0000;  // This has to be kind of reasonable because people declare Contexts on the stack and so forth...? Also must fit into 32 bits because that is the size of the value in Build_Options.
                        if value > CONTEXT_SIZE_MAX {
                            log_error("Command line: Invalid argument to -context_size. The context must be less than or equal to CONTEXT_SIZE_MAX, which is % (but the value provided was %). The size needs to be reasonable because contexts get declared on the stack, in other structs, and so forth. (If you think you should be able to exceed this limit, you can modify or replace Default_Metaprogram.)\n", CONTEXT_SIZE_MAX, value);
                        } else if value < size_of(Context_Base) {
                            log_error("Command line: Invalid argument to -context_size. The context must be at least as large as size_of(Context_Base), which is %.\n", size_of(Context_Base));
                        } else {
                            options.context_size_max = cast(s32) value;
                        }
                    } else {
                        log_error("Command line: Unable to parse an integer argument to context size; got '%'.\n", args[index]);
                    }
                case "-import_dir";
                    if index >= args.count-1 {
                        log_error("Command line: Missing argument to -import_dir.\n");
                        got_error = true;
                        break;
                    }

                    index += 1;
                    array_add(*modules_paths, args[index]);

                case "-no_color";
                    options.use_ansi_color = false;

                case "-verbose";
                    verbose = true;

                case;
                    log_error("Unknown argument '%'.\nExiting.\n", it);
                    got_error = true;
                    break;
            }

            continue;
        }

        // If we got here, it's a plain file. Add it to the array.
        array_add(*files, it);
    }

    if got_error {
        exit(1);
    }

    if do_check {
        p := array_add(*plugins_to_create);
        if check_bindings  p.name = "Check";
        else               p.name = "Check(CHECK_BINDINGS=false)";
    }

    // Now that we know what the plugins are, init them.
    success = init_plugins(plugins_to_create, *plugins, w);
    if !success {
        log_error("A plugin init() failed. Exiting.\n");
        exit(0);
    }

    if !(files || add_strings || run_strings) {
        if !(printed_help || printed_version)  log("You need to provide an argument telling the compiler what to compile! Sorry. Pass -help for help.\n");
        if printed_version && !printed_help exit(0);
        if !printed_help exit(1);  // If we printed help, we want to fall through to print help for all the modules, so don't exit yet.
    }


    old_wd := get_working_directory();
    absolute_files: [..] string;
    if files {
        if output_path             options.output_path            = output_path;
        if output_executable_name  options.output_executable_name = output_executable_name;

        array_reserve(*absolute_files, files.count);
        for files {
            absolute_file := it;
            if !is_absolute_path(it) {
                #if OS == .WINDOWS {
                    absolute_file = get_absolute_path(it);
                } else {
                    absolute_file = sprint("%/%", old_wd, it);
                }
            }

            array_add(*absolute_files, absolute_file);
        }

        basename, path := get_basename_and_path(absolute_files[0]);

        if basename || path {
            if !output_path {
                options.output_path = sprint("%/bin", old_wd);
                make_directory_if_it_does_not_exist(options.output_path);
                
                options.intermediate_path = sprint("%/.build", old_wd);
                make_directory_if_it_does_not_exist(options.intermediate_path);

                if verbose print("options.output_path = \"%\";\n", options.output_path);
            }

            if basename && !output_executable_name {
                options.output_executable_name = basename;
                if verbose print("options.output_executable_name = \"%\";\n", basename);
            }
        }

        if path {
            if verbose  print("Changing working directory to '%'.\n", path);
            set_working_directory(path);
        }
    }

    if printed_help {
        log("\n\n");

        for plugins {
            name := plugins_to_create[it_index].name;

            if it.log_help {
                log("---- Help for plugin '%': ----\n\n", name);
                it.log_help(it);
                log("\n");
            } else {
                log("---- Plugin '%' provides no help.\n", name);
            }
        }

        if !(files || run_strings) exit(0);
    }

    if modules_paths {
        prefix := "";
        if files {
            // Because modules_paths are intended to be relative to the source code, but these aren't coming from the
            // source code themselves, we need to tell the compiler where they live. We do this by just prefixing
            // the import_paths with the path to the first source file. This is weird and kind of a hack, but it's unclear
            // what is a better thing to do.
            basename, path := get_basename_and_path(absolute_files[0]);
            if path {
                for * modules_paths if !is_absolute_path(it.*) { it.* = tprint("%0%", path, it.*); }
            }
        }

        array_add(*modules_paths, ..options.import_path);  // Put these behind whatever the user specified.
        options.import_path = modules_paths;
    }

    options.compile_time_command_line = user_arguments;

    // Make a fake location...
    loc: Source_Code_Location;
    loc.fully_pathed_filename = ifx absolute_files then absolute_files[0] else tprint("%/fake_file.fake", old_wd);  // In case someone does a #run that imports stuff but adds no files!

    set_build_options(options, w, loc=loc);

    for plugins if it.before_intercept it.before_intercept(it, *intercept_flags);

    // As the compiler builds the target program, we can listen in on messages
    // that report the status of the program. In later examples we can use
    // these messages to do sophisticated things, but for now, we'll just
    // use them to report on the status of compilation.

    // To tell the compiler to give us messages, we need to call compiler_begin_intercept
    // before we add any code to the target workspace.
    compiler_begin_intercept(w, intercept_flags);

    if verbose {
        print("Input files: %\n", absolute_files);
        print("Add strings: %\n", add_strings);
        print("Run strings: %\n", run_strings);
        print("Plugins:     %\n", plugins_to_create);
    }

    for plugins      if it.add_source it.add_source(it);

    for absolute_files {
        add_build_file(it, w);
    }

    for add_strings  add_build_string(tprint("%;", it), w);
    for run_strings  add_build_string(tprint("#run %;\n", it), w); // run_strings is kind of redundant now! We could remove it.

    // Call message_loop(), which is a routine of ours below that will receive the messages.
    message_loop(w);

    // When we're done, message_loop will return.
    // We call compiler_end_intercept to tell the compiler we are done.
    compiler_end_intercept(w);

    for plugins if it.finish   it.finish  (it);
    for plugins if it.shutdown it.shutdown(it);

    {
        // None of the code in this file is intended to end up in an executable
        // of any kind. So, we tell the compiler not to make an executable for us:

        set_build_options_dc(.{do_output=false, write_added_strings=false});
    }
}

#run,stallable build();

//
// message_loop() runs the event loop that reads the messages.
// You can do whatever you want with those messages. The goal
// of this example is just to show the different kinds of messages,
// so we don't do anything crazy yet. But you can do some things
// that are crazy.
//
message_loop :: (w: Workspace) {
    while true {
        // We ask the compiler for the next message. If one is not available,
        // we will wait until it becomes available.
        message := compiler_wait_for_message();
        // Pass the message to all plugins.
        for plugins if it.message it.message(it, message);

        if message.kind == .COMPLETE break;
    }
}

#import "Basic";
#import "File";
#import "Compiler";
#import "Metaprogram_Plugins";
#import "File_Utilities";


verbose  := false;
plugins: [..] *Metaprogram_Plugin;


HELP_STRING :: #string DONE
Available Command-Line Arguments:

-add arg         Add the string 'arg' to the target program as code.
                 Example: -add "MY_VARIABLE :: 42";
-context_size n  Set the size of #Context, in bytes (you only need this if your program has a really big context).
                 Example: -context_size 2048
-debugger        If there is a crash in compile-time execution, drop into the interactive debugger.
-debug_for       Enable debugging of for_expansion macros. (Otherwise the debugger will never step into them to make stepping through for loops more convenient.)
-exe name        Set output_executable_name on the target workspace to 'name'.
-import_dir arg  Add this directory to the list of directories searched by #import. Can be
                     used multiple times.
-llvm            Use the LLVM backend by default (unless overridden by a metaprogram).
                     The LLVM backend is the default normally, so this isn't too useful.
-msvc_format     Use Visual Studio's message format for error messages.
-natvis          Use natvis compatible type names in debug info (array<T> instead of [] T, etc).
-no_backtrace_on_crash    Do not catch OS-level exceptions and print a stack trace when your program crashes.
                     Causes less code to be imported on startup. Depending on your OS (for example, on Windows),
                     crashes may look like silent exits.
-no_color        Disable ANSI terminal coloring in output messages.
-no_dce          Turn off dead code elimination. This is a temporary option,
                     provided because dead code elimination is a new and potentially
                     unstable feature. This will eventually be removed; the preferred way
                     to turn off dead code elimination is via Build_Options.
-no_split        Disable split modules when compiling with the LLVM backend.
-no_check        Do not import modules/Check and run it on the code. The result will be that you won't get
                     augmented error checking for stuff like print() calls. Use this if you want to substitute
                     your own error checking plugin, or for higher speeds in builds known to be good.
-no_check_bindings Disable checking of module bindings when running modules/Check. If modules/Check is not run
                     due to -no_check, this does nothing.
-no_inline       Disable inlining throughout the program (useful when debugging).
-o, -optimzed    Build an optimized build (and disable stack traces).
-od, -optimized_debug   Make a build that is a little less optimized, and keeps user-level stack traces (which do cost CPU cycles, but can help with debugging).
-output_path     Set the path where your output files (such as the executable) will go.
-quiet           Run the compiler in quiet mode (not outputting unnecessary text).
-run arg         Start a #run directive that parses and runs 'arg' as code.
                     Example: -run write_string(\"Hello!\n\")
                     (The extra backslashes are the shell's fault.)
-verbose         Output some extra information about what this metaprogram is doing.
-version         Print the version of the compiler.
-very_debug      Build a very_debug build, i.e. add more debugging facilities than usual, which will cause it to run slower but catch more problems.
-x64             Use the x64 backend by default (unless overridden by a metaprogram).

-                Every argument after - is ignored by the compiler itself,
                     and is passed to the user-level metaprogram for its own use.

Any argument not starting with a -, and before a - by itself, is the name of a file to compile.

Example:    jai -x64 program.jai - info for -the compile_time execution


==== Plugins ====

In the section before the lone dash, but after all other such arguments listed above,
you can have arguments that invoke plugins. These arguments begin with a + and then
the name of the plugin module to invoke.

Example:    jai -x64 program.jai -verbose +Icon -icon icon_filename.ico +Autorun

Each + invokes a new plugin, and all arguments between that + and the next + are sent
to that plugin. In the above example, the -icon is a command-line option handled only
by the Icon plugin (the name "-icon" is currently confusing and we'll change it!)

Here's an example that uses the lone dash as well as a plugin invocation:

Example:    jai -x64 program.jai +Icon -icon icon_filename.ico +Autorun - arguments for the invoked metaprogram go here


==== Compiler Hardcoded Options ====

There are also a few very tweaky compiler-front-end options that almost nobody
will ever care about. To see these, do:

            jai -- help


==== Other Functionality ====

Unlike most contemporary compilers, we don't have a huge number of arcane arguments
to control all kinds of minute things, that you have to spend a long time learning about.
Our philosophy is, it's much more effective to do configuration from code, where
you have a great deal of precise control over what's happening, options can be expressed
as easy-to-understand data structures, and so forth. To get started, look into Build_Options
in modules/Compiler/Compiler.jai, or look at how_to/400_workspaces.jai and
how_to/420_command_line.jai.

This text is generated by modules/Default_Metaprogram.jai, the code that starts up
when you run the compiler. You can read the text of this file to learn how all this works
(it's pretty simple!); you can make a copy of Default_Metaprogram and modify it
to work however you want.
DONE