|
4 | 4 | import json |
5 | 5 | import requests |
6 | 6 | import hashlib |
| 7 | +import platform |
| 8 | +from dataclasses import dataclass |
| 9 | +from typing import Optional |
7 | 10 |
|
8 | 11 | class Channel: |
9 | 12 | def __init__(self, kind: str) -> None: |
@@ -35,6 +38,45 @@ def __str__(self) -> str: |
35 | 38 | return self.channel_version |
36 | 39 | return f"{self.channel_version}.{self.feature_band.replace('0', 'x')}" |
37 | 40 |
|
| 41 | +# TODO: platform.system() doesn't handle musl - need separate detection |
| 42 | +def compute_platform_part(platform: str) -> str: |
| 43 | + lower = platform.lower() |
| 44 | + match lower: |
| 45 | + case "osx" | "freebsd" | "rhel.6" | "linux-musl" | "linux": |
| 46 | + return lower |
| 47 | + case "macos": |
| 48 | + return "osx" |
| 49 | + case "win" | "windows": |
| 50 | + return "win" |
| 51 | + |
| 52 | +def compute_arch_part(architecture: str) -> str: |
| 53 | + match architecture: |
| 54 | + case "arm64" | "aarch64": |
| 55 | + return "arm64" |
| 56 | + case "s390x" | "ppc64le" | "loongarch64": |
| 57 | + return architecture |
| 58 | + case _: |
| 59 | + return "x64" |
| 60 | + |
| 61 | +def compute_runtime_identifier(platform: str, architecture: str) -> str: |
| 62 | + platform_part = compute_platform_part(platform) |
| 63 | + arch_part = compute_arch_part(architecture) |
| 64 | + if platform_part is None or arch_part is None: |
| 65 | + raise Exception(f"Unable to compute runtime identifier for {platform}/{architecture}") |
| 66 | + return f"{platform_part}-{arch_part}" |
| 67 | + |
| 68 | + |
| 69 | +@dataclass |
| 70 | +class InstallRequest: |
| 71 | + channel: Channel |
| 72 | + runtime: Optional[str] |
| 73 | + architecture: str |
| 74 | + os: str |
| 75 | + |
| 76 | + @property |
| 77 | + def rid(self): |
| 78 | + return compute_runtime_identifier(self.os, self.architecture) |
| 79 | + |
38 | 80 | def download_releases_index() -> json: |
39 | 81 | return requests.get("https://dotnetcli.blob.core.windows.net/dotnet/release-metadata/releases-index.json", ).json() |
40 | 82 |
|
@@ -96,32 +138,42 @@ def download_file(url: str, hash: str): |
96 | 138 |
|
97 | 139 | return local_filename |
98 | 140 |
|
99 | | -def install_sdk(desired_channel: Channel, runtime_identifier: str): |
| 141 | + |
| 142 | + |
| 143 | +def install_sdk(request: InstallRequest): |
100 | 144 | index = download_releases_index() |
101 | 145 | if index is None: |
102 | 146 | raise Exception("Releases index could not be downloaded") |
103 | 147 | channels: list = index["releases-index"] |
104 | | - channel_information = pick_best_channel(channels, desired_channel) |
| 148 | + channel_information = pick_best_channel(channels, request.channel) |
105 | 149 | channel_version = channel_information["channel-version"] |
106 | 150 | if channel_information is None: |
107 | 151 | raise Exception(f"No matching channel could be found for desired channel '{channel_version}'") |
108 | 152 | channel_releases = download_releases_for_channel(channel_information) |
109 | 153 | if channel_releases is None: |
110 | 154 | raise Exception(f"No releases found for channel '{channel_version}'") |
111 | | - release_information = pick_best_release(channel_releases, desired_channel) |
| 155 | + release_information = pick_best_release(channel_releases, request.channel) |
112 | 156 | if release_information is None: |
113 | 157 | raise Exception(f"No best release found in channel '{channel_version}'") |
114 | 158 | release_version = release_information['release-version'] |
115 | | - sdk_information = pick_matching_sdk(release_information['sdks'], desired_channel) |
| 159 | + sdk_information = pick_matching_sdk(release_information['sdks'], request.channel) |
116 | 160 | if sdk_information is None: |
117 | 161 | raise Exception(f"No matching SDK found in release '{release_version}'") |
118 | | - file_to_download = pick_file_to_download(sdk_information['files'], runtime_identifier) |
| 162 | + file_to_download = pick_file_to_download(sdk_information['files'], request.rid) |
119 | 163 | if file_to_download is None: |
120 | | - raise Exception(f"No matching file for runtime identifier {runtime_identifier} found in release '{release_version}'") |
| 164 | + raise Exception(f"No matching file for runtime identifier {request.rid} found in release '{release_version}'") |
121 | 165 | download_file(file_to_download['url'], file_to_download['hash']) |
122 | 166 |
|
123 | 167 | if __name__ == "__main__": |
124 | 168 | parser = argparse.ArgumentParser("dotnet-install.py", description="%(prog)s ia a simple command line interface for obtaining the .NET SDK and Runtime", add_help=True, exit_on_error=True) |
125 | 169 | parser.add_argument("--channel", "-c", type=Channel, help="The release grouping to download from. Can have a variety of formats: sts, lts, two-part version number (8.0) or three-part version number in major.minor.patchxx format (8.0.2xx)") |
126 | | - args = parser.parse_args() |
127 | | - install_sdk(args.channel, 'win-x64') |
| 170 | + parser.add_argument("--runtime", help="Installs a shared runtime only, without the SDK", choices=["dotnet", "aspnetcore"]) |
| 171 | + parser.add_argument("--architecture", help="Architecture of the .NET binaries to be installed, defaults to the current system.", default=platform.machine()) |
| 172 | + parser.add_argument("--os", help="Specifies the operating system to be used when selecting the installer", default=platform.system()) |
| 173 | + try: |
| 174 | + args = parser.parse_args() |
| 175 | + request = InstallRequest(args.channel, args.runtime, args.architecture, args.os) |
| 176 | + install_sdk(request) |
| 177 | + except: |
| 178 | + parser.print_help() |
| 179 | + exit(1) |
0 commit comments