development
dart 216 lines 5.26 KB
Raw
1 import 'dart:ffi';
2 import 'dart:io';
3 import 'dart:isolate';
4
5 class OndeCliException implements Exception {
6 OndeCliException(this.message);
7
8 final String message;
9
10 @override
11 String toString() => message;
12 }
13
14 Future<int> runOndeCli(List<String> arguments) async {
15 final executablePath = await resolveExecutablePath();
16 await ensureExecutablePermissions(executablePath);
17
18 final process = await Process.start(
19 executablePath,
20 arguments,
21 mode: ProcessStartMode.inheritStdio,
22 workingDirectory: Directory.current.path,
23 );
24
25 return process.exitCode;
26 }
27
28 Future<String> resolveExecutablePath() async {
29 final version = await packageVersion();
30 final cacheDirectory = await cliCacheDirectory(version);
31 final executablePath = [
32 cacheDirectory.path,
33 runtimeIdentifier(),
34 executableName(),
35 ].join(Platform.pathSeparator);
36
37 final executableFile = File(executablePath);
38 if (!await executableFile.exists()) {
39 await downloadExecutable(version, executableFile);
40 }
41
42 return executablePath;
43 }
44
45 Future<void> downloadExecutable(String version, File executableFile) async {
46 final parent = executableFile.parent;
47 if (!await parent.exists()) {
48 await parent.create(recursive: true);
49 }
50
51 final tempFile = File('${executableFile.path}.download');
52 if (await tempFile.exists()) {
53 await tempFile.delete();
54 }
55
56 final client = HttpClient();
57 try {
58 final request = await client.getUrl(downloadUri(version));
59 final response = await request.close();
60
61 if (response.statusCode != HttpStatus.ok) {
62 throw OndeCliException(
63 'Failed to download onde ${version} for ${runtimeIdentifier()} '
64 '(HTTP ${response.statusCode}).',
65 );
66 }
67
68 final sink = tempFile.openWrite();
69 await response.pipe(sink);
70 await sink.close();
71
72 if (await executableFile.exists()) {
73 await executableFile.delete();
74 }
75
76 await tempFile.rename(executableFile.path);
77 } on OndeCliException {
78 rethrow;
79 } catch (error) {
80 throw OndeCliException('Failed to download the native onde binary: $error');
81 } finally {
82 client.close(force: true);
83 }
84 }
85
86 Uri downloadUri(String version) {
87 return Uri.parse(
88 'https://github.com/ondeinference/onde-cli/releases/download/'
89 'v$version/${releaseAssetName()}',
90 );
91 }
92
93 Future<Directory> cliCacheDirectory(String version) async {
94 final home = homeDirectoryPath();
95 return Directory(
96 [home, '.onde', 'cli', version].join(Platform.pathSeparator),
97 );
98 }
99
100 String homeDirectoryPath() {
101 final env = Platform.environment;
102 final home = env['HOME'];
103 if (home != null && home.isNotEmpty) {
104 return home;
105 }
106
107 final userProfile = env['USERPROFILE'];
108 if (userProfile != null && userProfile.isNotEmpty) {
109 return userProfile;
110 }
111
112 final homeDrive = env['HOMEDRIVE'];
113 final homePath = env['HOMEPATH'];
114 if (homeDrive != null && homePath != null) {
115 return '$homeDrive$homePath';
116 }
117
118 throw OndeCliException('Could not resolve the home directory for onde.');
119 }
120
121 Future<String> packageVersion() async {
122 final packageRoot = await resolvePackageRoot();
123 final pubspecFile = File(packageRoot.resolve('pubspec.yaml').toFilePath());
124
125 if (!await pubspecFile.exists()) {
126 throw OndeCliException('Could not read pubspec.yaml for onde_cli.');
127 }
128
129 final lines = await pubspecFile.readAsLines();
130 for (final line in lines) {
131 if (line.startsWith('version: ')) {
132 return line.substring('version: '.length).trim().replaceAll('"', '');
133 }
134 }
135
136 throw OndeCliException('Could not find a version in pubspec.yaml.');
137 }
138
139 Future<Uri> resolvePackageRoot() async {
140 final packageUri = await Isolate.resolvePackageUri(
141 Uri.parse('package:onde_cli/onde_cli.dart'),
142 );
143
144 if (packageUri == null) {
145 throw OndeCliException(
146 'Failed to resolve the installed Dart package path.',
147 );
148 }
149
150 return packageUri.resolve('../');
151 }
152
153 String runtimeIdentifier() {
154 final architecture = architectureName();
155
156 if (Platform.isMacOS) {
157 return 'darwin-$architecture';
158 }
159 if (Platform.isWindows) {
160 return 'windows-$architecture';
161 }
162 if (Platform.isLinux) {
163 return 'linux-$architecture';
164 }
165
166 throw OndeCliException(
167 'onde does not support this operating system through the Dart package.',
168 );
169 }
170
171 String architectureName() {
172 final abi = Abi.current().toString().toLowerCase();
173 if (abi.contains('arm64')) {
174 return 'arm64';
175 }
176 if (abi.contains('x64')) {
177 return 'x64';
178 }
179
180 throw OndeCliException(
181 'onde does not support the current architecture through the Dart package.',
182 );
183 }
184
185 String releaseAssetName() {
186 final architecture = architectureName() == 'x64' ? 'amd64' : 'arm64';
187
188 if (Platform.isMacOS) {
189 return 'onde-macos-$architecture';
190 }
191 if (Platform.isLinux) {
192 return 'onde-linux-$architecture';
193 }
194 if (Platform.isWindows) {
195 return 'onde-win-$architecture.exe';
196 }
197
198 throw OndeCliException(
199 'onde does not support this operating system through the Dart package.',
200 );
201 }
202
203 String executableName() => Platform.isWindows ? 'onde.exe' : 'onde';
204
205 Future<void> ensureExecutablePermissions(String executablePath) async {
206 if (Platform.isWindows) {
207 return;
208 }
209
210 final result = await Process.run('chmod', ['755', executablePath]);
211 if (result.exitCode != 0) {
212 throw OndeCliException(
213 'Failed to mark the native onde binary as executable.',
214 );
215 }
216 }