Appearance
| 1 | namespace Driver is | |
| 2 | use System.Exception | |
| 3 | ||
| 4 | use IO.Std | |
| 5 | use IO.Path | |
| 6 | use IO.File | |
| 7 | use IO.Directory | |
| 8 | ||
| 9 | use Collections.Iterable | |
| 10 | use Collections.LIST | |
| 11 | use Collections.SET | |
| 12 | ||
| 13 | use System.Text.RegularExpressions.Regex | |
| 14 | use System.Text.RegularExpressions.Match | |
| 15 | ||
| 16 | use System.Runtime.InteropServices.RuntimeInformation | |
| 17 | use System.Runtime.InteropServices.OSPlatform | |
| 18 | ||
| 19 | use Ghul.Pipes | |
| 20 | ||
| 21 | use IoC | |
| 22 | use Logging | |
| 23 | use Compiler | |
| 24 | ||
| 25 | use Analysis.ANALYSER | |
| 26 | ||
| 27 | class MAIN is | |
| 28 | paths: Driver.PATH_CONFIG | |
| 29 | project_name: string | |
| 30 | container: IoC.CONTAINER | |
| 31 | compiler: COMPILER | |
| 32 | flags: GLOBAL_BUILD_FLAGS | |
| 33 | output_file_name_generator: OUTPUT_FILE_NAME_GENERATOR | |
| 34 | assemblies: LIST[string] | |
| 35 | ||
| 36 | // Assemblies named with `--reference`: imported as well as the | |
| 37 | // reference set, where `-a` names the whole of it. | |
| 38 | references: LIST[string] | |
| 39 | ||
| 40 | // Each is `<path>=<logical-name>`, collected from `--resource` | |
| 41 | // and written into the manifest resource table at the end of | |
| 42 | // the build. | |
| 43 | resources: LIST[(path: string, logical_name: string)] | |
| 44 | ||
| 45 | ghul_source_files: LIST[string] | |
| 46 | analyse_files: LIST[string] | |
| 47 | module_version: string | |
| 48 | assembly_info: Semantic.DotNet.ASSEMBLY_INFO | |
| 49 | ||
| 50 | want_format: bool | |
| 51 | ||
| 52 | want_dump_tokens: bool | |
| 53 | want_compile_server: bool | |
| 54 | ||
| 55 | // --check-complete parses the one source named and prints whether it | |
| 56 | // is complete, incomplete or invalid, running no other pass. | |
| 57 | want_check_complete: bool | |
| 58 | ||
| 59 | // --format-in-place rewrites each source file rather than printing | |
| 60 | // to standard output. The write goes through a sibling temporary | |
| 61 | // file and an atomic rename, so a failure anywhere leaves the | |
| 62 | // original untouched. | |
| 63 | want_format_in_place: bool | |
| 64 | ||
| 65 | // --annotate-inferred prints each named source with the types the | |
| 66 | // build inferred written in; --annotate-inferred-in-place rewrites | |
| 67 | // the files instead. | |
| 68 | want_annotate_inferred: bool | |
| 69 | ||
| 70 | // --strip-annotations <selection> prints each named source with | |
| 71 | // the chosen written annotations left out, for a program to be | |
| 72 | // rebuilt with less than its author wrote; `list` prints the | |
| 73 | // numbered sites instead, each with the span its annotation | |
| 74 | // occupies. | |
| 75 | want_strip_annotations: bool | |
| 76 | strip_selection: string | |
| 77 | want_annotate_inferred_in_place: bool | |
| 78 | ||
| 79 | // Column the formatter wraps at. 100 unless --format-width says otherwise: source is | |
| 80 | // read in narrower places than an editor - a fixed-width block on a web page, a prose | |
| 81 | // column beside it - and what fits there is the caller's to say. | |
| 82 | format_width: int | |
| 83 | ||
| 84 | compiler_version: string => | |
| 85 | let assembly_version = | |
| 86 | System.Reflection.Assembly | |
| 87 | .get_entry_assembly()! | |
| 88 | .get_custom_attributes(typeof System.Reflection.AssemblyInformationalVersionAttribute, false) | |
| 89 | |> map(va => cast System.Reflection.AssemblyInformationalVersionAttribute?(va)!) | |
| 90 | |> first() | |
| 91 | ||
| 92 | in | |
| 93 | if assembly_version? then | |
| 94 | "{assembly_version.informational_version}" | |
| 95 | else | |
| 96 | "v0.0.0-unknown.1" | |
| 97 | fi | |
| 98 | ||
| 99 | entry(arguments: string[]) static is | |
| 100 | let full_arguments = LIST[string](arguments.count + 1) | |
| 101 | ||
| 102 | add_arguments(full_arguments, arguments) | |
| 103 | ||
| 104 | let instance = MAIN(LIST[string](full_arguments)) | |
| 105 | si | |
| 106 | ||
| 107 | add_arguments(result: LIST[string], source: Iterable[string]) static is | |
| 108 | let seen_files = SET() | |
| 109 | ||
| 110 | add_arguments(seen_files, result, source) | |
| 111 | si | |
| 112 | ||
| 113 | add_arguments(seen_files: SET[string], result: LIST[string], source: Iterable[string]) static is | |
| 114 | for argument in source do | |
| 115 | if argument.starts_with('@') then | |
| 116 | read_arguments_from_file(seen_files, result, argument.substring(1)) | |
| 117 | elif argument.length > 0 then | |
| 118 | result.add(argument) | |
| 119 | fi | |
| 120 | od | |
| 121 | si | |
| 122 | ||
| 123 | read_arguments_from_file(seen_files: SET[string], result: LIST[string], path: string) static is | |
| 124 | // Keyed on the resolved path rather than on what was written, so | |
| 125 | // that two spellings of one file - one relative and one absolute, | |
| 126 | // or one with a redundant ./ - are recognised as the same file and | |
| 127 | // a cycle between them ends. | |
| 128 | let key = Path.get_full_path(path) | |
| 129 | ||
| 130 | if seen_files.contains(key) then | |
| 131 | return | |
| 132 | fi | |
| 133 | ||
| 134 | seen_files.add(key) | |
| 135 | ||
| 136 | let parser = ARGUMENTS_PARSER() | |
| 137 | ||
| 138 | let text = File.read_all_text(path) | |
| 139 | ||
| 140 | let arguments = parser.parse_arguments(text) | |
| 141 | ||
| 142 | add_arguments(seen_files, result, arguments) | |
| 143 | si | |
| 144 | ||
| 145 | // This constructor is the whole program: it sets its state up | |
| 146 | // inside the try, and the only paths that leave without doing | |
| 147 | // so report the failure and exit the process. | |
| 148 | @suppress("field-definite-assignment") | |
| 149 | init(arguments: LIST[string]) is | |
| 150 | let result mut = 1 | |
| 151 | ||
| 152 | try | |
| 153 | Std.input_encoding = System.Text.UTF8Encoding(false) | |
| 154 | Std.output_encoding = System.Text.UTF8Encoding(false) | |
| 155 | ||
| 156 | if arguments.count == 0 then | |
| 157 | Std.out.write("ghūl {compiler_version}\n") | |
| 158 | Std.out.flush() | |
| 159 | ||
| 160 | System.Environment.exit(0) | |
| 161 | fi | |
| 162 | ||
| 163 | if USAGE.is_requested(arguments) then | |
| 164 | Std.out.write(USAGE.text) | |
| 165 | Std.out.flush() | |
| 166 | ||
| 167 | System.Environment.exit(0) | |
| 168 | fi | |
| 169 | ||
| 170 | container = IoC.CONTAINER.instance | |
| 171 | ||
| 172 | paths = container.path_config | |
| 173 | flags = container.build_flags | |
| 174 | compiler = COMPILER() | |
| 175 | output_file_name_generator = OUTPUT_FILE_NAME_GENERATOR() | |
| 176 | assembly_info = container.assembly_info | |
| 177 | module_version = "0:0:0:0" | |
| 178 | ||
| 179 | parse_flags(arguments) | |
| 180 | ||
| 181 | if want_check_complete then | |
| 182 | check_complete() | |
| 183 | ||
| 184 | System.Environment.exit(0) | |
| 185 | fi | |
| 186 | ||
| 187 | check_assemblies_exist() | |
| 188 | ||
| 189 | IoC.CONTAINER.instance.assemblies.import(assemblies, references) | |
| 190 | ||
| 191 | if flags.want_analyse then | |
| 192 | analyse() | |
| 193 | ||
| 194 | System.Environment.exit(0) | |
| 195 | fi | |
| 196 | ||
| 197 | if want_compile_server then | |
| 198 | serve_compiles() | |
| 199 | ||
| 200 | System.Environment.exit(0) | |
| 201 | fi | |
| 202 | ||
| 203 | if want_dump_tokens then | |
| 204 | dump_tokens() | |
| 205 | ||
| 206 | System.Environment.exit(0) | |
| 207 | fi | |
| 208 | ||
| 209 | if want_format then | |
| 210 | format_files() | |
| 211 | ||
| 212 | System.Environment.exit(0) | |
| 213 | fi | |
| 214 | ||
| 215 | if want_strip_annotations then | |
| 216 | strip_annotations_files() | |
| 217 | ||
| 218 | System.Environment.exit(0) | |
| 219 | fi | |
| 220 | ||
| 221 | // A warning rather than an error: every ghul.runtime release so | |
| 222 | // far passes AssemblyMetadataAttribute a single string, and a | |
| 223 | // build using one must not start failing. The attribute is left | |
| 224 | // out either way, so nothing refers to a missing constructor. | |
| 225 | for problem in assembly_info.resolve(name => IoC.CONTAINER.instance.assemblies.find_type(name)) do | |
| 226 | container.logger.warn(Source.LOCATION.internal, Semantic.DotNet.ASSEMBLY_INFO.UNMATCHED, problem) | |
| 227 | od | |
| 228 | ||
| 229 | start_build() | |
| 230 | ||
| 231 | compiler.post_parse() | |
| 232 | ||
| 233 | compiler.build() | |
| 234 | ||
| 235 | result = finish_build() | |
| 236 | ||
| 237 | if want_annotate_inferred /\ result == 0 then | |
| 238 | annotate_inferred_files() | |
| 239 | fi | |
| 240 | catch e: INPUT_EXCEPTION | |
| 241 | Std.error.write_line("error: {e.message}") | |
| 242 | Std.error.flush() | |
| 243 | ||
| 244 | result = 1 | |
| 245 | catch e: Exception | |
| 246 | Std.error.write(e) | |
| 247 | Std.error.write("\n") | |
| 248 | Std.error.flush() | |
| 249 | ||
| 250 | result = 1 | |
| 251 | yrt | |
| 252 | ||
| 253 | if result == 0 then | |
| 254 | Std.error.write("*** succeeded ***") | |
| 255 | else | |
| 256 | Std.error.write("!!! failed !!!") | |
| 257 | fi | |
| 258 | ||
| 259 | Std.error.write("\n") | |
| 260 | ||
| 261 | System.Environment.exit(result) | |
| 262 | si | |
| 263 | ||
| 264 | // Every -a names an assembly the compiler is to read. A path that is | |
| 265 | // not there is always a mistake in what supplied it - nothing is | |
| 266 | // compiled more successfully for having skipped one - so it is | |
| 267 | // reported rather than left to surface as whatever the load happens to | |
| 268 | // throw, or worse, as the missing symbols of a reference that was | |
| 269 | // silently dropped. | |
| 270 | check_assemblies_exist() is | |
| 271 | let message = missing_assemblies_message(assemblies |> cat(references)) | |
| 272 | ||
| 273 | if message? then | |
| 274 | throw INPUT_EXCEPTION(message) | |
| 275 | fi | |
| 276 | si | |
| 277 | ||
| 278 | // The message naming the assembly paths that are not there, or null | |
| 279 | // when they all are. | |
| 280 | missing_assemblies_message(paths: Iterable[string]) -> string? static is | |
| 281 | let missing = LIST[string]() | |
| 282 | ||
| 283 | for path in paths do | |
| 284 | if !File.exists(path) then | |
| 285 | missing.add(path) | |
| 286 | fi | |
| 287 | od | |
| 288 | ||
| 289 | if missing.count == 0 then | |
| 290 | return null | |
| 291 | fi | |
| 292 | ||
| 293 | let names = missing |> join(", ") | |
| 294 | ||
| 295 | return | |
| 296 | if missing.count == 1 then | |
| 297 | "reference assembly not found: {names}" | |
| 298 | else | |
| 299 | "reference assemblies not found: {names}" | |
| 300 | fi | |
| 301 | si | |
| 302 | ||
| 303 | // The value a flag takes from the argument after it. | |
| 304 | next_flag_value(arguments: Collections.Iterator[string], flag: string) -> string static is | |
| 305 | if !arguments.move_next() then | |
| 306 | throw INPUT_EXCEPTION("{flag} needs a value") | |
| 307 | fi | |
| 308 | ||
| 309 | return arguments.current.trim() | |
| 310 | si | |
| 311 | ||
| 312 | parse_flags(args: Iterable[string]) is | |
| 313 | assemblies = LIST() | |
| 314 | references = LIST() | |
| 315 | resources = LIST() | |
| 316 | ghul_source_files = LIST() | |
| 317 | ||
| 318 | format_width = 100 | |
| 319 | ||
| 320 | let args_iterator = args.iterator | |
| 321 | ||
| 322 | flags.want_compile_up_to_expressions = true | |
| 323 | flags.want_compile_expressions = true | |
| 324 | flags.want_assembler = true | |
| 325 | flags.want_executable = true | |
| 326 | ||
| 327 | let want_type_check = false | |
| 328 | let do_not_want_type_check = false | |
| 329 | ||
| 330 | let deprecated_no_warn = (flag: string, slug: string) is | |
| 331 | Std.error.write_line("warning: {flag} is deprecated; use --suppress {slug} instead") | |
| 332 | container.logger.suppress(slug) | |
| 333 | si | |
| 334 | ||
| 335 | let conditional_defines = Collections.LIST[string]() | |
| 336 | ||
| 337 | for s in args_iterator do | |
| 338 | if s =~ "-A" \/ s =~ "--analyse" then | |
| 339 | flags.want_analyse = true | |
| 340 | flags.want_assembler = false | |
| 341 | flags.want_executable = false | |
| 342 | elif s =~ "-G" \/ s =~ "--type-check" then | |
| 343 | flags.want_assembler = false | |
| 344 | flags.want_executable = false | |
| 345 | elif s =~ "-g" \/ s =~ "--no-type-check" then | |
| 346 | flags.want_compile_up_to_expressions = false | |
| 347 | flags.want_compile_expressions = false | |
| 348 | flags.want_assembler = false | |
| 349 | flags.want_executable = false | |
| 350 | elif s =~ "-E" \/ s =~ "--ignore-errors" then | |
| 351 | flags.ignore_errors = true | |
| 352 | elif s =~ "-S" \/ s =~ "--assembler" then | |
| 353 | flags.want_executable = false | |
| 354 | elif s =~ "--library" then | |
| 355 | flags.want_library = true | |
| 356 | elif s =~ "--debug" then | |
| 357 | flags.want_debug = true | |
| 358 | elif s =~ "--trace-inference" then | |
| 359 | Semantic.INFERENCE_TRACE.enable("-") | |
| 360 | elif s =~ "--trace-inference-file" then | |
| 361 | Semantic.INFERENCE_TRACE.enable(next_flag_value(args_iterator, s)) | |
| 362 | elif s =~ "--define" then | |
| 363 | conditional_defines.add(next_flag_value(args_iterator, s)) | |
| 364 | elif s =~ "--entry" then | |
| 365 | container.ir_context.entry_point_name = next_flag_value(args_iterator, s) | |
| 366 | container.ir_context.entry_point_name_is_explicit = true | |
| 367 | elif s =~ "--v3" then | |
| 368 | conditional_defines.add("v3") | |
| 369 | elif s =~ "--test-run" then | |
| 370 | flags.is_test_run = true | |
| 371 | elif s =~ "--keep-duplicate-diagnostics" then | |
| 372 | flags.keep_duplicate_diagnostics = true | |
| 373 | elif s =~ "--msbuild-diagnostics" then | |
| 374 | flags.want_msbuild_diagnostics = true | |
| 375 | elif s =~ "-N" \/ s =~ "--dotnet" then | |
| 376 | // do nothing - .NET is the only supported option | |
| 377 | elif s =~ "-o" \/ s =~ "--output" then | |
| 378 | output_file_name_generator.force(next_flag_value(args_iterator, s)) | |
| 379 | elif s =~ "-p" \/ s =~ "--library-prefix" then | |
| 380 | paths.library_prefix = next_flag_value(args_iterator, s) | |
| 381 | elif s =~ "-a" \/ s =~ "--assembly" then | |
| 382 | assemblies.add(next_flag_value(args_iterator, s)) | |
| 383 | elif s =~ "--reference" then | |
| 384 | references.add(next_flag_value(args_iterator, s)) | |
| 385 | elif s =~ "--resource" then | |
| 386 | let resource_arg = next_flag_value(args_iterator, s) | |
| 387 | let separator_index = resource_arg.last_index_of('=') | |
| 388 | ||
| 389 | if separator_index > 0 /\ separator_index < resource_arg.length - 1 then | |
| 390 | resources.add(( | |
| 391 | path = resource_arg.substring(0, separator_index), | |
| 392 | logical_name = resource_arg.substring(separator_index + 1))) | |
| 393 | else | |
| 394 | Std.error.write_line("warning: ignoring garbled --resource value: {resource_arg}") | |
| 395 | fi | |
| 396 | elif s =~ "--assembly-info-string" then | |
| 397 | let info_arg = next_flag_value(args_iterator, s) | |
| 398 | let separator_index = info_arg.index_of('=') | |
| 399 | ||
| 400 | if separator_index > 0 then | |
| 401 | assembly_info.add_attribute( | |
| 402 | info_arg.substring(0, separator_index).trim(), | |
| 403 | info_arg.substring(separator_index + 1).trim()) | |
| 404 | else | |
| 405 | Std.error.write_line("warning: ignoring garbled assembly info: {info_arg}") | |
| 406 | fi | |
| 407 | elif s =~ "--version" then | |
| 408 | let version_string = next_flag_value(args_iterator, s) | |
| 409 | ||
| 410 | let version_string_first_part = version_string.split(['-', '+']) |> first() | |
| 411 | ||
| 412 | let version: System.Version mut | |
| 413 | ||
| 414 | try | |
| 415 | if | |
| 416 | version_string_first_part? | |
| 417 | then | |
| 418 | let version_parts = version_string_first_part.split(['.']) | |
| 419 | ||
| 420 | if version_parts.count > 4 then | |
| 421 | throw Exception("invalid version number") | |
| 422 | fi | |
| 423 | ||
| 424 | let b = System.Text.StringBuilder() | |
| 425 | let seen_any mut = false | |
| 426 | ||
| 427 | for p in version_parts do | |
| 428 | if seen_any then | |
| 429 | b.append(":") | |
| 430 | fi | |
| 431 | ||
| 432 | b.append(int.parse(p)) | |
| 433 | ||
| 434 | seen_any = true | |
| 435 | od | |
| 436 | ||
| 437 | for i in version_parts.count..4 do | |
| 438 | b.append(":0") | |
| 439 | od | |
| 440 | ||
| 441 | module_version = b.to_string() | |
| 442 | else | |
| 443 | throw Exception("invalid version number") | |
| 444 | fi | |
| 445 | ||
| 446 | catch ex: Exception | |
| 447 | Std.error.write_line("warning: ignoring garbled version number: {version_string}") | |
| 448 | yrt | |
| 449 | elif s =~ "--sdk-ref-path" then | |
| 450 | paths.sdk_reference_path = next_flag_value(args_iterator, s) | |
| 451 | elif s =~ "--target-framework" then | |
| 452 | paths.target_framework = next_flag_value(args_iterator, s) | |
| 453 | elif s =~ "--exclude-runtime-symbols" then | |
| 454 | flags.exclude_runtime_symbols = true | |
| 455 | elif s =~ "--show-analysis-stats" then | |
| 456 | flags.want_analysis_stats = true | |
| 457 | elif s =~ "--no-analysis-heap-watchdog" then | |
| 458 | flags.no_analysis_heap_watchdog = true | |
| 459 | elif s =~ "--incremental-analysis" then | |
| 460 | flags.want_incremental_analysis = true | |
| 461 | elif s =~ "--no-incremental-analysis" then | |
| 462 | flags.want_incremental_analysis = false | |
| 463 | elif s =~ "--analysis-idle-timeout" then | |
| 464 | let seconds_string = next_flag_value(args_iterator, s) | |
| 465 | ||
| 466 | try | |
| 467 | flags.analysis_idle_timeout_seconds = int.parse(seconds_string) | |
| 468 | catch ex: Exception | |
| 469 | Std.error.write_line("warning: ignoring garbled --analysis-idle-timeout value: {seconds_string}") | |
| 470 | yrt | |
| 471 | elif s =~ "--default-use" then | |
| 472 | for name in next_flag_value(args_iterator, s).split([',']) do | |
| 473 | let trimmed = name.trim() | |
| 474 | ||
| 475 | if trimmed.length > 0 then | |
| 476 | flags.default_uses.add(trimmed) | |
| 477 | fi | |
| 478 | od | |
| 479 | elif s =~ "--implicit-default-use" then | |
| 480 | flags.want_implicit_default_use = true | |
| 481 | elif s =~ "--submission" then | |
| 482 | flags.submission_name = next_flag_value(args_iterator, s) | |
| 483 | elif s =~ "--global-namespace" then | |
| 484 | flags.want_global_namespace = true | |
| 485 | elif s =~ "--underscore-access" then | |
| 486 | let mode = next_flag_value(args_iterator, s) | |
| 487 | if mode =~ "private" then | |
| 488 | flags.underscore_access = UnderscoreAccess.PRIVATE | |
| 489 | elif mode =~ "protected" then | |
| 490 | flags.underscore_access = UnderscoreAccess.PROTECTED | |
| 491 | elif mode =~ "legacy" then | |
| 492 | flags.underscore_access = UnderscoreAccess.LEGACY | |
| 493 | else | |
| 494 | Std.error.write_line("warning: unrecognized --underscore-access mode: {mode} (expected legacy, private or protected)") | |
| 495 | fi | |
| 496 | elif s =~ "--compile-server" then | |
| 497 | want_compile_server = true | |
| 498 | elif s =~ "--dump-tokens" then | |
| 499 | want_dump_tokens = true | |
| 500 | elif s =~ "--check-complete" then | |
| 501 | want_check_complete = true | |
| 502 | elif s =~ "--format" then | |
| 503 | want_format = true | |
| 504 | elif s =~ "--format-in-place" then | |
| 505 | want_format = true | |
| 506 | want_format_in_place = true | |
| 507 | elif s =~ "--strip-annotations" then | |
| 508 | want_strip_annotations = true | |
| 509 | strip_selection = next_flag_value(args_iterator, s) | |
| 510 | elif s =~ "--annotate-inferred" then | |
| 511 | want_annotate_inferred = true | |
| 512 | flags.want_assembler = false | |
| 513 | flags.want_executable = false | |
| 514 | elif s =~ "--annotate-inferred-in-place" then | |
| 515 | want_annotate_inferred = true | |
| 516 | want_annotate_inferred_in_place = true | |
| 517 | flags.want_assembler = false | |
| 518 | flags.want_executable = false | |
| 519 | elif s =~ "--format-width" then | |
| 520 | let width = next_flag_value(args_iterator, s) | |
| 521 | ||
| 522 | try | |
| 523 | format_width = int.parse(width) | |
| 524 | ||
| 525 | if format_width < 20 then | |
| 526 | Std.error.write_line("warning: --format-width {format_width} is too narrow to lay anything out; using 20") | |
| 527 | ||
| 528 | format_width = 20 | |
| 529 | fi | |
| 530 | catch ex: Exception | |
| 531 | Std.error.write_line("warning: --format-width expects a number, not '{width}'") | |
| 532 | yrt | |
| 533 | elif s =~ "--warn-implicit-mutable-let" then | |
| 534 | Std.error.write_line("warning: --warn-implicit-mutable-let is deprecated; the implicit-mutable-let warning is now an error and the flag has no effect") | |
| 535 | elif s =~ "--no-warn-definite-return" then | |
| 536 | deprecated_no_warn("--no-warn-definite-return", "definite-return") | |
| 537 | elif s =~ "--no-warn-definite-assignment" then | |
| 538 | deprecated_no_warn("--no-warn-definite-assignment", "definite-assignment") | |
| 539 | elif s =~ "--no-warn-non-optional" then | |
| 540 | deprecated_no_warn("--no-warn-non-optional", "non-optional") | |
| 541 | elif s =~ "--no-warn-impossible-cast" then | |
| 542 | deprecated_no_warn("--no-warn-impossible-cast", "impossible-cast") | |
| 543 | elif s =~ "--no-warn-narrowing-always-succeeds" then | |
| 544 | deprecated_no_warn("--no-warn-narrowing-always-succeeds", "narrowing-always-succeeds") | |
| 545 | elif s =~ "--no-warn-likely-override-mismatch" then | |
| 546 | deprecated_no_warn("--no-warn-likely-override-mismatch", "likely-override-mismatch") | |
| 547 | elif s =~ "--suppress" then | |
| 548 | for slug in next_flag_value(args_iterator, s).split([',']) do | |
| 549 | let trimmed = slug.trim() | |
| 550 | if trimmed.length > 0 then | |
| 551 | container.logger.suppress(trimmed) | |
| 552 | fi | |
| 553 | od | |
| 554 | elif s =~ "--warn-as-error" then | |
| 555 | for slug in next_flag_value(args_iterator, s).split([',']) do | |
| 556 | let trimmed = slug.trim() | |
| 557 | if trimmed =~ "all" then | |
| 558 | container.logger.set_all_warnings_are_errors(true) | |
| 559 | elif trimmed.length > 0 then | |
| 560 | container.logger.promote_to_error(trimmed) | |
| 561 | fi | |
| 562 | od | |
| 563 | elif s =~ "--warn-as-hint" then | |
| 564 | for slug in next_flag_value(args_iterator, s).split([',']) do | |
| 565 | let trimmed = slug.trim() | |
| 566 | if trimmed.length > 0 then | |
| 567 | container.logger.demote_to_hint(trimmed) | |
| 568 | fi | |
| 569 | od | |
| 570 | elif s =~ "--warn-as-info" then | |
| 571 | for slug in next_flag_value(args_iterator, s).split([',']) do | |
| 572 | let trimmed = slug.trim() | |
| 573 | if trimmed.length > 0 then | |
| 574 | container.logger.demote_to_info(trimmed) | |
| 575 | fi | |
| 576 | od | |
| 577 | elif s =~ "--warn" then | |
| 578 | for slug in next_flag_value(args_iterator, s).split([',']) do | |
| 579 | let trimmed = slug.trim() | |
| 580 | if trimmed.length > 0 then | |
| 581 | container.logger.unsuppress(trimmed) | |
| 582 | fi | |
| 583 | od | |
| 584 | elif s =~ "--inlay" then | |
| 585 | for kind in next_flag_value(args_iterator, s).split([',']) do | |
| 586 | let trimmed = kind.trim() | |
| 587 | ||
| 588 | if trimmed.length > 0 then | |
| 589 | if Logging.INLAY_KINDS.is_known(trimmed) then | |
| 590 | container.logger.enable_inlay_kind(trimmed) | |
| 591 | else | |
| 592 | Std.error.write_line("warning: ignoring unknown inlay kind: {trimmed}") | |
| 593 | fi | |
| 594 | fi | |
| 595 | od | |
| 596 | elif s =~ "--no-inlay" then | |
| 597 | for kind in next_flag_value(args_iterator, s).split([',']) do | |
| 598 | let trimmed = kind.trim() | |
| 599 | ||
| 600 | if trimmed.length > 0 then | |
| 601 | if Logging.INLAY_KINDS.is_known(trimmed) then | |
| 602 | container.logger.disable_inlay_kind(trimmed) | |
| 603 | else | |
| 604 | Std.error.write_line("warning: ignoring unknown inlay kind: {trimmed}") | |
| 605 | fi | |
| 606 | fi | |
| 607 | od | |
| 608 | elif s.starts_with('-') then | |
| 609 | Std.error.write_line("warning: ignoring unknown option: {s}") | |
| 610 | elif SOURCE_FILE_CATEGORIZER.is_ghul(s) then | |
| 611 | output_file_name_generator.seen_file(s) | |
| 612 | ghul_source_files.add(s) | |
| 613 | elif IO.Directory.exists(s) then | |
| 614 | ghul_source_files.add(s) | |
| 615 | else | |
| 616 | Std.error.write_line("warning: ignoring unrecognized argument: {s}") | |
| 617 | fi | |
| 618 | od | |
| 619 | ||
| 620 | flags.mark_valid() | |
| 621 | ||
| 622 | if flags.want_msbuild_diagnostics then | |
| 623 | container.want_msbuild_logger(Std.error) | |
| 624 | elif flags.is_test_run then | |
| 625 | container.want_human_readable_logger(Std.error) | |
| 626 | container.want_duplicate_diagnostics() | |
| 627 | fi | |
| 628 | ||
| 629 | if flags.keep_duplicate_diagnostics then | |
| 630 | container.want_duplicate_diagnostics() | |
| 631 | fi | |
| 632 | ||
| 633 | container.conditional_compilation.set_is_enabled(conditional_defines) | |
| 634 | si | |
| 635 | ||
| 636 | // One word on standard output, and a zero exit status whatever the | |
| 637 | // answer: a non-zero status means the check itself could not run. | |
| 638 | check_complete() is | |
| 639 | if ghul_source_files.count != 1 then | |
| 640 | throw INPUT_EXCEPTION("--check-complete takes exactly one source file") | |
| 641 | fi | |
| 642 | ||
| 643 | let path = ghul_source_files[0] | |
| 644 | ||
| 645 | let completeness = compiler.check_complete(path, File.open_text(path)) | |
| 646 | ||
| 647 | Std.out.write_line( | |
| 648 | case completeness | |
| 649 | when Syntax.Parsers.COMPLETENESS.COMPLETE then "complete" | |
| 650 | when Syntax.Parsers.COMPLETENESS.INCOMPLETE then "incomplete" | |
| 651 | else "invalid" | |
| 652 | esac | |
| 653 | ) | |
| 654 | ||
| 655 | Std.out.flush() | |
| 656 | si | |
| 657 | ||
| 658 | dump_tokens() is | |
| 659 | for path in expand_format_sources() do | |
| 660 | try | |
| 661 | let tokenizer = Lexical.TOKENIZER( | |
| 662 | IoC.CONTAINER.instance.logger, | |
| 663 | path, | |
| 664 | File.open_text(path), | |
| 665 | false | |
| 666 | ) | |
| 667 | ||
| 668 | let pair mut = tokenizer.read_token() | |
| 669 | ||
| 670 | while pair.token != Lexical.TOKEN.END_OF_INPUT do | |
| 671 | let value = pair.value_string | |
| 672 | .replace("\\", "\\\\") | |
| 673 | .replace("\t", "\\t") | |
| 674 | .replace("\n", "\\n") | |
| 675 | .replace("\r", "\\r") | |
| 676 | ||
| 677 | Std.out.write_line("{path}\t{pair.location.start_line}\t{pair.location.start_column}\t{pair.location.end_line}\t{pair.location.end_column}\t{pair.token}\t{value}") | |
| 678 | ||
| 679 | pair = tokenizer.read_token() | |
| 680 | od | |
| 681 | catch ex: Exception | |
| 682 | Std.error.write_line("error: could not tokenize {path}: {ex.message}") | |
| 683 | yrt | |
| 684 | od | |
| 685 | ||
| 686 | Std.out.flush() | |
| 687 | si | |
| 688 | ||
| 689 | format_files() is | |
| 690 | for path in expand_format_sources() do | |
| 691 | try | |
| 692 | let source_file = | |
| 693 | compiler.parse(path, File.open_text(path), flags.want_compile_up_to_expressions, flags.want_compile_expressions, false) | |
| 694 | ||
| 695 | let formatter = | |
| 696 | Syntax.Process.Printer.FORMATTER(source_file.trivia, format_width) | |
| 697 | ||
| 698 | let formatted = formatter.format(source_file.definition) | |
| 699 | ||
| 700 | if want_format_in_place then | |
| 701 | write_formatted_in_place(path, formatted) | |
| 702 | else | |
| 703 | Std.out.write(formatted) | |
| 704 | Std.out.flush() | |
| 705 | fi | |
| 706 | catch ex: Exception | |
| 707 | Std.error.write_line("error: could not format {path}: {ex.message}") | |
| 708 | yrt | |
| 709 | od | |
| 710 | si | |
| 711 | ||
| 712 | // Each named source printed with the selected written annotations | |
| 713 | // left out. Under `list` the numbered sites go to standard output | |
| 714 | // in place of the source; otherwise the source goes there and the | |
| 715 | // sites to standard error, one per line. | |
| 716 | strip_annotations_files() is | |
| 717 | for path in expand_format_sources() do | |
| 718 | let source_file = | |
| 719 | compiler.parse(path, File.open_text(path), false, false, false) | |
| 720 | ||
| 721 | let formatter = | |
| 722 | Syntax.Process.Printer.FORMATTER(source_file.trivia, format_width) | |
| 723 | ||
| 724 | let stripper = Syntax.Process.Printer.ANNOTATION_STRIPPER(strip_selection) | |
| 725 | ||
| 726 | formatter.stripper = stripper | |
| 727 | ||
| 728 | let formatted = formatter.format(source_file.definition) | |
| 729 | ||
| 730 | if strip_selection =~ "list" then | |
| 731 | for site in stripper.sites do | |
| 732 | Std.out.write_line(site) | |
| 733 | od | |
| 734 | else | |
| 735 | Std.out.write(formatted) | |
| 736 | ||
| 737 | for site in stripper.sites do | |
| 738 | Std.error.write_line(site) | |
| 739 | od | |
| 740 | fi | |
| 741 | ||
| 742 | Std.out.flush() | |
| 743 | Std.error.flush() | |
| 744 | od | |
| 745 | si | |
| 746 | ||
| 747 | // Each named source printed with the types the build settled | |
| 748 | // at its inferred sites. The built tree is not what is printed: | |
| 749 | // the passes splice nodes the source never had, so the types are | |
| 750 | // collected from it by position and written into a fresh parse. | |
| 751 | annotate_inferred_files() is | |
| 752 | let annotated mut = 0 | |
| 753 | let skipped mut = 0 | |
| 754 | ||
| 755 | for source_file in compiler.source_files do | |
| 756 | let path = source_file.file_name | |
| 757 | ||
| 758 | if !(ghul_source_files |> any(f => f =~ path)) then | |
| 759 | continue | |
| 760 | fi | |
| 761 | ||
| 762 | let annotations = | |
| 763 | Syntax.Process.INFERRED_ANNOTATIONS(container.logger, container.symbol_table, container.namespaces) | |
| 764 | ||
| 765 | annotations.collect(source_file.definition) | |
| 766 | ||
| 767 | annotated = annotated + annotations.annotated | |
| 768 | skipped = skipped + annotations.skipped | |
| 769 | ||
| 770 | let use reader = File.open_text(path) | |
| 771 | ||
| 772 | let fresh = | |
| 773 | compiler.parse(path, reader, false, false, false, Logging.DIAGNOSTICS_STORE()) | |
| 774 | ||
| 775 | let formatter = | |
| 776 | Syntax.Process.Printer.FORMATTER(fresh.trivia, format_width) | |
| 777 | ||
| 778 | formatter.annotations = annotations | |
| 779 | ||
| 780 | let formatted = formatter.format(fresh.definition) | |
| 781 | ||
| 782 | if want_annotate_inferred_in_place then | |
| 783 | write_formatted_in_place(path, formatted) | |
| 784 | else | |
| 785 | Std.out.write(formatted) | |
| 786 | Std.out.flush() | |
| 787 | fi | |
| 788 | od | |
| 789 | ||
| 790 | Std.error.write_line("annotated {annotated} inferred sites, skipped {skipped}") | |
| 791 | si | |
| 792 | ||
| 793 | // Under --format a directory argument names a tree of sources: every | |
| 794 | // .ghul file beneath it, recursively. | |
| 795 | expand_format_sources() -> LIST[string] is | |
| 796 | let paths = LIST[string]() | |
| 797 | ||
| 798 | for path in ghul_source_files do | |
| 799 | if IO.Directory.exists(path) then | |
| 800 | for file in IO.Directory.get_files(path, "*.ghul", IO.SearchOption.ALL_DIRECTORIES) do | |
| 801 | paths.add(file) | |
| 802 | od | |
| 803 | else | |
| 804 | paths.add(path) | |
| 805 | fi | |
| 806 | od | |
| 807 | ||
| 808 | return paths | |
| 809 | si | |
| 810 | ||
| 811 | // Write through a sibling temporary file and rename it over the | |
| 812 | // original: the rename is atomic, so a failure anywhere before it | |
| 813 | // leaves the original untouched rather than truncated. | |
| 814 | write_formatted_in_place(path: string, contents: string) is | |
| 815 | let temporary = "{path}.tmp" | |
| 816 | ||
| 817 | File.write_all_text(temporary, contents) | |
| 818 | File.move(temporary, path, true) | |
| 819 | si | |
| 820 | ||
| 821 | start_build() is | |
| 822 | queue_source_files() | |
| 823 | ||
| 824 | if flags.want_assembler then | |
| 825 | container.value_boxer.want_boxing = true | |
| 826 | fi | |
| 827 | ||
| 828 | let module_name = | |
| 829 | Path.get_file_name_without_extension(output_file_name_generator.result) ?? "module" | |
| 830 | ||
| 831 | install_assembly_emitter(module_name) | |
| 832 | si | |
| 833 | ||
| 834 | finish_build() -> int is | |
| 835 | container.logger.write_all_diagnostics(container.logger_writer, container.logger_formatter) | |
| 836 | ||
| 837 | if container.logger.any_errors /\ !flags.ignore_errors then | |
| 838 | return 1 | |
| 839 | elif container.logger.is_poisoned then | |
| 840 | container.logger.write_poison_messages() | |
| 841 | ||
| 842 | Std.error.write_line("internal error") | |
| 843 | return 2 | |
| 844 | fi | |
| 845 | ||
| 846 | if flags.want_assembler then | |
| 847 | return finish_build_dotnet() | |
| 848 | else | |
| 849 | return 0 | |
| 850 | fi | |
| 851 | si | |
| 852 | ||
| 853 | finish_build_dotnet() -> int is | |
| 854 | let output_file mut = output_file_name_generator.result | |
| 855 | ||
| 856 | if output_file.last_index_of('.') < 0 then | |
| 857 | output_file = "{output_file}.{if flags.want_library then "dll" else "exe" fi}" | |
| 858 | fi | |
| 859 | ||
| 860 | if !flags.want_executable then | |
| 861 | return 0 | |
| 862 | fi | |
| 863 | ||
| 864 | for r in resources do | |
| 865 | container.ir_context.srm_assembly_emitter.add_resource( | |
| 866 | r.logical_name, | |
| 867 | File.read_all_bytes(r.path)) | |
| 868 | od | |
| 869 | ||
| 870 | container.ir_context.srm_assembly_emitter.write_to(output_file) | |
| 871 | ||
| 872 | let result mut = 0 | |
| 873 | ||
| 874 | // A library assembly is loaded into a host's runtime config, | |
| 875 | // not run directly, so it needs neither its own | |
| 876 | // `.runtimeconfig.json` nor the executable bit. | |
| 877 | if !flags.want_library then | |
| 878 | let runtime_config_file = | |
| 879 | if output_file.last_index_of('.') == output_file.length - 4 then | |
| 880 | "{output_file.substring(0, output_file.last_index_of('.'))}.runtimeconfig.json" | |
| 881 | else | |
| 882 | "{output_file}.runtimeconfig.json" | |
| 883 | fi | |
| 884 | ||
| 885 | let resolver = TARGET_FRAMEWORK_RESOLVER() | |
| 886 | let tfm = resolver.resolve_tfm(paths.target_framework, paths.sdk_reference_path, paths.host_target_framework) | |
| 887 | let framework_version = resolver.resolve_framework_version(tfm) | |
| 888 | ||
| 889 | let runtime_config = File.create_text(runtime_config_file) | |
| 890 | runtime_config.write("{{\"runtimeOptions\":{{\"tfm\":\"{tfm}\",\"framework\":{{\"name\":\"Microsoft.NETCore.App\",\"version\":\"{framework_version}\"}} }} }}") | |
| 891 | runtime_config.close() | |
| 892 | ||
| 893 | if !RuntimeInformation.is_o_s_platform(OSPlatform.windows) then | |
| 894 | let chmod = System.Diagnostics.Process.start("/bin/chmod", "+x {output_file}") | |
| 895 | ||
| 896 | chmod.wait_for_exit() | |
| 897 | ||
| 898 | result = chmod.exit_code | |
| 899 | ||
| 900 | if result != 0 then | |
| 901 | Std.error.write_line("compiled successfully but failed to set executable bit on resulting binary: {output_file}") | |
| 902 | fi | |
| 903 | fi | |
| 904 | fi | |
| 905 | ||
| 906 | return result | |
| 907 | si | |
| 908 | ||
| 909 | queue_source_files() is | |
| 910 | for file in ghul_source_files do | |
| 911 | if file.ends_with(".ghul") then | |
| 912 | queue_source_file(file, false) | |
| 913 | elif IO.Directory.exists(file) then | |
| 914 | Std.error.write_line( | |
| 915 | "warning: ignoring directory argument {file}: a directory is expanded only by --format") | |
| 916 | fi | |
| 917 | od | |
| 918 | si | |
| 919 | ||
| 920 | queue_source_file(path: string, is_internal_file: bool) is | |
| 921 | if flags.want_analyse then | |
| 922 | analyse_files.add(path) | |
| 923 | ||
| 924 | return | |
| 925 | fi | |
| 926 | ||
| 927 | let use reader = File.open_text(path) | |
| 928 | ||
| 929 | compiler.parse_and_queue(path, reader, flags.want_compile_up_to_expressions, flags.want_compile_expressions, is_internal_file) | |
| 930 | si | |
| 931 | ||
| 932 | // Every mode that runs the passes needs an emitter, whether or not | |
| 933 | // it writes an assembly: a pass's prologue runs before the pass | |
| 934 | // decides it has nothing to emit, so analysis reaches one too. It | |
| 935 | // is installed from here rather than built with the context, | |
| 936 | // because the emitter is reached through the container the | |
| 937 | // context is being built into. | |
| 938 | install_assembly_emitter(module_name: string) is | |
| 939 | container.ir_context.install_srm_assembly_emitter( | |
| 940 | module_name, module_version, flags.want_library) | |
| 941 | si | |
| 942 | ||
| 943 | serve_compiles() is | |
| 944 | COMPILE_SERVER(compiler, container, module_version, Std.`in, Std.out).serve() | |
| 945 | si | |
| 946 | ||
| 947 | analyse() is | |
| 948 | // Analysis names no output, and writes none: this emitter is | |
| 949 | // what the passes hold, not something they fill. | |
| 950 | install_assembly_emitter("module") | |
| 951 | ||
| 952 | analyse_files = LIST() | |
| 953 | ||
| 954 | container.watchdog.heap_check_disabled = flags.no_analysis_heap_watchdog | |
| 955 | ||
| 956 | // The analyser reads protocol frames and EDIT payloads from stdin a | |
| 957 | // character at a time. Console.In (Std.`in`) buffers only 256 bytes, | |
| 958 | // so a large EDIT payload costs thousands of read() syscalls. Read | |
| 959 | // instead through a 256 KB buffer over the raw standard-input | |
| 960 | // stream: that exceeds every compiler source file (largest ~230 KB, | |
| 961 | // p99 ~44 KB), so a single-file EDIT is drained in one read() and a | |
| 962 | // whole-project EDIT in few. A buffered read still returns as soon | |
| 963 | // as any bytes are available — a fully-received request is never | |
| 964 | // delayed waiting for the buffer to fill. | |
| 965 | let analyser_input = | |
| 966 | IO.StreamReader( | |
| 967 | Std.open_standard_input(), | |
| 968 | System.Text.UTF8Encoding(false), | |
| 969 | false, | |
| 970 | 262144 | |
| 971 | ) | |
| 972 | ||
| 973 | let analyser = ANALYSER( | |
| 974 | compiler, | |
| 975 | container.timers, | |
| 976 | container.symbol_table, | |
| 977 | container.symbol_use_locations, | |
| 978 | container.symbol_definition_locations, | |
| 979 | container.completer, | |
| 980 | container.signature_help, | |
| 981 | analyser_input, | |
| 982 | Std.out, | |
| 983 | analyse_files, | |
| 984 | flags, | |
| 985 | container.watchdog | |
| 986 | ) | |
| 987 | ||
| 988 | Std.error.write_line("ghūl compiler {compiler_version}: serving analysis requests") | |
| 989 | ||
| 990 | analyser.run() | |
| 991 | si | |
| 992 | si | |
| 993 | si | |
| 994 |