Appearance
| 1 | namespace Driver is | |
| 2 | // Locates the .NET reference-assembly directory to compile against when the | |
| 3 | // compiler is invoked with source files but neither `-a` nor | |
| 4 | // `--sdk-ref-path`. The install root comes from DOTNET_ROOT, or is derived | |
| 5 | // from the runtime directory the compiler is itself running on; under it, | |
| 6 | // the highest Microsoft.NETCore.App.Ref pack matching the target | |
| 7 | // framework's version supplies `ref/<tfm>/`. Every step answers null rather | |
| 8 | // than throwing, so a layout this doesn't recognise surfaces as the | |
| 9 | // caller's "no reference assemblies supplied" error. | |
| 10 | class SDK_REFERENCE_PATH_RESOLVER is | |
| 11 | init() is si | |
| 12 | ||
| 13 | resolve(target_framework: string) -> string? is | |
| 14 | let dotnet_root = discover_dotnet_root() | |
| 15 | ||
| 16 | if !dotnet_root? then | |
| 17 | return null | |
| 18 | fi | |
| 19 | ||
| 20 | return resolve_under(dotnet_root, target_framework) | |
| 21 | si | |
| 22 | ||
| 23 | resolve_under(dotnet_root: string, target_framework: string) -> string? is | |
| 24 | let version = framework_version(target_framework) | |
| 25 | ||
| 26 | if !version? then | |
| 27 | return null | |
| 28 | fi | |
| 29 | ||
| 30 | let packs_root = IO.Path.join([dotnet_root, "packs", "Microsoft.NETCore.App.Ref"]) | |
| 31 | ||
| 32 | if !IO.Directory.exists(packs_root) then | |
| 33 | return null | |
| 34 | fi | |
| 35 | ||
| 36 | let pack = latest_ref_pack(packs_root, version) | |
| 37 | ||
| 38 | if !pack? then | |
| 39 | return null | |
| 40 | fi | |
| 41 | ||
| 42 | let reference_path = IO.Path.join([pack, "ref", target_framework]) | |
| 43 | ||
| 44 | if !IO.Directory.exists(reference_path) then | |
| 45 | return null | |
| 46 | fi | |
| 47 | ||
| 48 | return reference_path | |
| 49 | si | |
| 50 | ||
| 51 | // "net10.0" -> "10.0", matching ref-pack directory names like "10.0.10". | |
| 52 | // A moniker that isn't `net` followed by a version has no pack of this | |
| 53 | // shape to find, so it answers null rather than globbing on a prefix | |
| 54 | // that would match nothing (or, for a short value, indexing off the end). | |
| 55 | framework_version(target_framework: string) -> string? is | |
| 56 | if target_framework.length <= 3 \/ !target_framework.starts_with("net") then | |
| 57 | return null | |
| 58 | fi | |
| 59 | ||
| 60 | let first = target_framework[3] | |
| 61 | ||
| 62 | if first < '0' \/ first > '9' then | |
| 63 | return null | |
| 64 | fi | |
| 65 | ||
| 66 | return target_framework.substring(3, target_framework.length - 3) | |
| 67 | si | |
| 68 | ||
| 69 | // The runtime directory is `shared/Microsoft.NETCore.App/<version>/` | |
| 70 | // under the install root, so the root is three levels up. Trailing | |
| 71 | // separators have to go first, or get_directory_name consumes one of | |
| 72 | // them instead of a path segment and the walk stops a level short. | |
| 73 | install_root_from_runtime_directory(runtime_directory: string) -> string? is | |
| 74 | let trimmed = strip_trailing_separators(runtime_directory) | |
| 75 | ||
| 76 | if trimmed.length == 0 then | |
| 77 | return null | |
| 78 | fi | |
| 79 | ||
| 80 | let current mut = trimmed | |
| 81 | ||
| 82 | for i in 0..3 do | |
| 83 | let parent = IO.Path.get_directory_name(current) | |
| 84 | ||
| 85 | if !parent? then | |
| 86 | return null | |
| 87 | fi | |
| 88 | ||
| 89 | if parent.length == 0 then | |
| 90 | return null | |
| 91 | fi | |
| 92 | ||
| 93 | current = parent | |
| 94 | od | |
| 95 | ||
| 96 | return current | |
| 97 | si | |
| 98 | ||
| 99 | strip_trailing_separators(path: string) -> string is | |
| 100 | let result mut = path | |
| 101 | ||
| 102 | while result.length > 0 /\ is_separator(result[result.length - 1]) do | |
| 103 | result = result.substring(0, result.length - 1) | |
| 104 | od | |
| 105 | ||
| 106 | return result | |
| 107 | si | |
| 108 | ||
| 109 | // Both of the running platform's separators: on Windows that covers the | |
| 110 | // native `\` as well as `/`, and on Unix `\` is a legal filename | |
| 111 | // character and deliberately isn't stripped. | |
| 112 | is_separator(c: char) -> bool => | |
| 113 | c == IO.Path.directory_separator_char \/ c == IO.Path.alt_directory_separator_char | |
| 114 | ||
| 115 | latest_ref_pack(packs_root: string, framework_version: string) -> string? is | |
| 116 | let best: string? mut = null | |
| 117 | let best_version: System.Version? mut = null | |
| 118 | ||
| 119 | for directory in IO.Directory.get_directories(packs_root, "{framework_version}.*") do | |
| 120 | let version_text = IO.Path.get_file_name(directory) ?? "" | |
| 121 | ||
| 122 | try | |
| 123 | let version = System.Version.parse(version_text) | |
| 124 | ||
| 125 | if !best_version? \/ version.compare_to(best_version) > 0 then | |
| 126 | best_version = version | |
| 127 | best = directory | |
| 128 | fi | |
| 129 | catch ex: System.Exception | |
| 130 | // not a version directory; skip | |
| 131 | yrt | |
| 132 | od | |
| 133 | ||
| 134 | return best | |
| 135 | si | |
| 136 | ||
| 137 | discover_dotnet_root() -> string? is | |
| 138 | let env_root = System.Environment.get_environment_variable("DOTNET_ROOT") | |
| 139 | ||
| 140 | if env_root? /\ env_root.length > 0 /\ IO.Directory.exists(env_root) then | |
| 141 | return env_root | |
| 142 | fi | |
| 143 | ||
| 144 | let derived = | |
| 145 | install_root_from_runtime_directory( | |
| 146 | System.Runtime.InteropServices.RuntimeEnvironment.get_runtime_directory()) | |
| 147 | ||
| 148 | if !derived? \/ !IO.Directory.exists(derived) then | |
| 149 | return null | |
| 150 | fi | |
| 151 | ||
| 152 | return derived | |
| 153 | si | |
| 154 | si | |
| 155 | si |