Skip to content

Commit 8433200

Browse files
committed
feature: Initial commit
0 parents  commit 8433200

9 files changed

+407
-0
lines changed

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Files and directories created by pub.
2+
.dart_tool/
3+
.packages
4+
5+
# Conventional directory for build outputs.
6+
build/
7+
8+
# Omit committing pubspec.lock for library packages; see
9+
# https://dart.dev/guides/libraries/private-files#pubspeclock.
10+
pubspec.lock

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## 0.1.0
2+
3+
- Initial version.

README.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# gibadb
2+
A tool to easily install the Android SDK command-line and platform tools.
3+
4+
**For developers:**
5+
_This README describes the CLI tool that ships with this Dart package. For package
6+
documentation, consult the appropriate [README](README_PACKAGE.md)._
7+
8+
## Features
9+
- [x] Cross-platform support
10+
- [x] Windows
11+
- [x] MacOS
12+
- [x] Linux
13+
- [x] Upgradeable platform tools
14+
- [x] Minimal, but a standard SDK installation (Android Studio compatible)
15+
16+
## Obtaining
17+
Binaries can be found in the [releases](https://github.com/hacker1024/gibadb/releases) page.
18+
19+
<details>
20+
<summary>
21+
Alternatively, for those with the Dart SDK installed, pub can be used to activate
22+
the package...
23+
</summary>
24+
25+
```
26+
$ dart pub global activate --source git https://github.com/hacker1024/gibadb.git
27+
```
28+
</details>
29+
30+
The following external tools are required:
31+
32+
- Java
33+
- p7zip or `unzip` (macOS and Linux)
34+
- PowerShell 5.0+ (Windows)
35+
36+
## Usage
37+
38+
1. Open a terminal (or command prompt)
39+
2. Run `gibadb`
40+
3. Add the required lines to your PATH
41+
42+
### Options
43+
44+
```
45+
$ gibadb --help
46+
47+
Usage: gibadb.dart [options] [SDK installation directory]
48+
Install the Android SDK command-line and platform tools.
49+
50+
If no SDK installation directory is provided, a common, platform-specific location is chosen.
51+
-v, --verbose Show more installation details.
52+
--[no-]launch-path-settings Open the system path settings after installation.
53+
(defaults to on)
54+
--[no-]platform-tools Install the SDK platform tools as well as the base SDK command-line tools.
55+
(defaults to on)
56+
-a, --archive=<command-line tools archive> Use an existing SDK command-line tools archive, instead of downloading one.
57+
--keep-archive Don't delete the SDK command-line tools archive.
58+
-h, --help Show the usage information.
59+
```

README_PACKAGE.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# android_platform_tools_installer_ui
2+
_An embeddable UI to install the Android SDK Command-Line Tools and Platform Tools._
3+
4+
## Usage
5+
See the [`gibadb`](bin/gibadb.dart) script for example usage.

analysis_options.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
include: package:lint/analysis_options_package.yaml

bin/gibadb.dart

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import 'dart:io';
2+
3+
import 'package:adb_tools/adb_tools.dart';
4+
import 'package:android_platform_tools_installer_ui/android_platform_tools_installer_ui.dart';
5+
import 'package:args/args.dart';
6+
7+
Future<void> main(List<String> arguments) async {
8+
final argParser = ArgParser()
9+
..addFlag(
10+
'verbose',
11+
abbr: 'v',
12+
help: 'Show more installation details.',
13+
negatable: false,
14+
)
15+
..addFlag(
16+
'launch-path-settings',
17+
help: 'Open the system path settings after installation.',
18+
defaultsTo: true,
19+
)
20+
..addFlag(
21+
'platform-tools',
22+
help:
23+
'Install the SDK platform tools as well as the base SDK command-line tools.',
24+
defaultsTo: true,
25+
)
26+
..addOption(
27+
'archive',
28+
abbr: 'a',
29+
help:
30+
'Use an existing SDK command-line tools archive, instead of downloading one.',
31+
valueHelp: 'command-line tools archive',
32+
)
33+
..addFlag(
34+
'keep-archive',
35+
help: "Don't delete the SDK command-line tools archive.",
36+
negatable: false,
37+
)
38+
..addFlag(
39+
'help',
40+
abbr: 'h',
41+
help: 'Show the usage information.',
42+
negatable: false,
43+
);
44+
45+
void printUsage(StringSink output) {
46+
output
47+
..writeln(
48+
'Usage: ${Platform.script.pathSegments.last} [options] [SDK installation directory]',
49+
)
50+
..writeln('Install the Android SDK command-line and platform tools.')
51+
..writeln()
52+
..writeln(
53+
'If no SDK installation directory is provided, a common, platform-specific location is chosen.',
54+
)
55+
..writeln(argParser.usage);
56+
}
57+
58+
final ArgResults argResults;
59+
try {
60+
argResults = argParser.parse(arguments);
61+
} on FormatException catch (e) {
62+
stderr
63+
..writeln(e.message)
64+
..writeln();
65+
printUsage(stderr);
66+
exitCode = 2;
67+
return;
68+
}
69+
70+
final verbose = argResults['verbose']! as bool;
71+
final launchPathSettings = argResults['launch-path-settings']! as bool;
72+
final installPlatformTools = argResults['platform-tools']! as bool;
73+
final existingCmdlineToolsArchive = argResults['archive'] as String?;
74+
final keepCmdlineToolsArchive = argResults['keep-archive']! as bool;
75+
final showUsageInformation = argResults['help']! as bool;
76+
final sdkInstallationDirectoryPath =
77+
argResults.rest.isEmpty ? null : argResults.rest[0];
78+
79+
if (showUsageInformation) {
80+
printUsage(stdout);
81+
return;
82+
}
83+
84+
final File? existingArchiveFile;
85+
if (existingCmdlineToolsArchive != null) {
86+
existingArchiveFile = File(existingCmdlineToolsArchive);
87+
if (!existingArchiveFile.existsSync()) {
88+
stderr.writeln(
89+
'The specified commandline-tools archive file does not exist.');
90+
exitCode = -1;
91+
return;
92+
}
93+
} else {
94+
existingArchiveFile = null;
95+
}
96+
97+
final Directory? sdkRoot;
98+
if (sdkInstallationDirectoryPath != null) {
99+
sdkRoot = Directory(sdkInstallationDirectoryPath);
100+
if (!sdkRoot.parent.existsSync()) {
101+
stderr.writeln(
102+
'The specified SDK installation parent directory does not exist.');
103+
exitCode = -1;
104+
return;
105+
}
106+
sdkRoot.createSync();
107+
} else {
108+
sdkRoot = null;
109+
}
110+
111+
String formatMessage(String message, {required bool isError}) =>
112+
'${isError ? '!' : '*'} $message';
113+
void writeMessage(String message) =>
114+
stdout.writeln(formatMessage(message, isError: false));
115+
void writeError(String message) =>
116+
stderr.writeln(formatMessage(message, isError: true));
117+
118+
try {
119+
await installAndroidSdk(
120+
sdkRoot: sdkRoot,
121+
existingArchiveFile: existingArchiveFile,
122+
deleteArchiveFile: !keepCmdlineToolsArchive,
123+
launchPathSettings: launchPathSettings,
124+
verbose: verbose,
125+
messageFormatter: formatMessage,
126+
installPlatformTools: installPlatformTools,
127+
);
128+
} on CmdlineToolsInstallUnsupportedPlatformException {
129+
writeError('Could not install command-line tools: Unsupported platform!');
130+
exitCode = -2;
131+
return;
132+
} on CmdlineToolsExtractFailedException catch (e) {
133+
writeError('Could not extract command-line tools: error ${e.errorCode}');
134+
exitCode = -3;
135+
return;
136+
} on CmdlineToolsPrepareFailedException catch (e) {
137+
writeError('Could not prepare command-line tools.');
138+
writeError(
139+
e.fileSystemException
140+
.toString()
141+
.substring('FileSystemException: '.length),
142+
);
143+
exitCode = -4;
144+
return;
145+
} on PlatformToolsInstallFailedException catch (e) {
146+
writeError('Could not install platform tools: error ${e.errorCode}');
147+
exitCode = -5;
148+
return;
149+
}
150+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
library android_platform_tools_installer_ui;
2+
3+
export 'src/android_platform_tools_installer_ui.dart';
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import 'dart:async';
2+
import 'dart:io';
3+
4+
import 'package:adb_tools/adb_tools.dart';
5+
import 'package:path/path.dart';
6+
import 'package:progressbar2/progressbar2.dart';
7+
8+
/// Installs the Android SDK, and, if [installPlatformTools] is true, the SDK
9+
/// platform tools, to the given [sdkRoot] directory.
10+
///
11+
/// Returns a [Future] that completes when the installation finishes.
12+
///
13+
/// If [output] is given, messages will be logged to it. Otherwise, standard I/O
14+
/// will be used. If standard I/O is used, subprocess output will not be
15+
/// streamed as messages and will instead be sent directly to standard output.
16+
/// If an [existingArchiveFile] is provided, it will be used instead of
17+
/// downloading the latest one available.
18+
/// If [deleteArchiveFile] is true, the downloaded SDK archive file will be
19+
/// deleted as soon as it is no longer needed.
20+
/// If a [messageFormatter] is provided, messages will be sent through it before
21+
/// being outputted.
22+
/// If [verbose] is true, subprocess will be run with verbose flags, and
23+
/// output from more subprocesses will be shown. Note that the latter cannot be
24+
/// prevented at all when no [output] sink is provided, even if [verbose] is
25+
/// false.
26+
///
27+
/// [launchPathSettings] and [installPlatformTools] are passed directly to
28+
/// [installCmdlineTools].
29+
///
30+
/// This function relies on the same external tools as [installCmdlineTools].
31+
Future<void> installAndroidSdk({
32+
StringSink? output,
33+
Directory? sdkRoot,
34+
File? existingArchiveFile,
35+
bool deleteArchiveFile = true,
36+
bool launchPathSettings = true,
37+
bool verbose = false,
38+
String Function(String message, {required bool isError}) messageFormatter =
39+
_unitMessageFormatter,
40+
bool installPlatformTools = true,
41+
}) async {
42+
// Set up output channels.
43+
final messageSink = output ?? stdout;
44+
final errorSink = output ?? stderr;
45+
void writeMessage(String message) =>
46+
messageSink.writeln(messageFormatter(message, isError: false));
47+
void writeError(String message) =>
48+
errorSink.writeln(messageFormatter(message, isError: true));
49+
50+
// Use the provided SDK root, or determine a platform-appropriate one if none
51+
// is provided.
52+
sdkRoot ??= _determineSdkRoot();
53+
54+
// Download the command-line tools archive if required, using a pretty
55+
// progress bar if outputting to a terminal.
56+
final File archiveFile;
57+
if (existingArchiveFile == null) {
58+
final ProgressCallback onReceiveProgress;
59+
if (output == null && stdout.hasTerminal) {
60+
writeMessage('Downloading SDK command-line tools:');
61+
final progressBar = ProgressBar(
62+
formatter: (current, total, progress, elapsed) =>
63+
'[$current/$total] ${ProgressBar.formatterBarToken} [${(progress * 100).floor()}%]',
64+
total: 0,
65+
);
66+
onReceiveProgress = (count, total) {
67+
progressBar.total = total;
68+
progressBar.value = count;
69+
progressBar.render();
70+
};
71+
} else {
72+
String format(int count, int total) =>
73+
'Downloading SDK command-line tools: $count/$total (${((count / total) * 100).floor()}%)';
74+
onReceiveProgress = output == null
75+
? (count, total) => writeMessage(format(count, total))
76+
: (count, total) => writeMessage(format(count, total));
77+
}
78+
archiveFile = await downloadCmdlineToolsArchive(
79+
sdkRoot,
80+
onReceiveProgress: onReceiveProgress,
81+
);
82+
} else {
83+
archiveFile = existingArchiveFile;
84+
}
85+
86+
late final List<Directory> pathEntries;
87+
Type? currentProgressStage;
88+
await for (final progress in installCmdlineTools(
89+
sdkRoot,
90+
archiveFile,
91+
deleteArchiveFile: deleteArchiveFile,
92+
launchPathSettings: launchPathSettings,
93+
verbose: verbose,
94+
installPlatformTools: installPlatformTools,
95+
inheritStdio: output == null,
96+
)) {
97+
final isNewProgressStage = progress.runtimeType != currentProgressStage;
98+
if (isNewProgressStage) currentProgressStage = progress.runtimeType;
99+
final bool messageIsVerbose;
100+
101+
if (progress is CmdlineToolsInstallExtracting) {
102+
if (isNewProgressStage) {
103+
writeMessage('Extracting SDK tools...');
104+
}
105+
if (!progress.usingNativeUnzip) {
106+
writeError('Could not use native unzip tool');
107+
}
108+
messageIsVerbose = true; // Classify extraction output as verbose.
109+
} else if (progress is CmdlineToolsInstallInstallingPlatformTools) {
110+
if (isNewProgressStage) {
111+
writeMessage('Installing platform tools...');
112+
}
113+
messageIsVerbose = false;
114+
} else if (progress is CmdlineToolsInstallCompleted) {
115+
pathEntries = progress.directPathEntries.toList(growable: false);
116+
messageIsVerbose = false;
117+
} else {
118+
messageIsVerbose = false;
119+
}
120+
121+
final message = progress.message;
122+
if (message != null && (verbose || !messageIsVerbose)) {
123+
message.isError ? writeError(message.text) : writeMessage(message.text);
124+
}
125+
}
126+
127+
writeMessage(
128+
'Android SDK installed. Remember to add the following directories to your PATH:',
129+
);
130+
for (final path in pathEntries) {
131+
messageSink.write(' ');
132+
messageSink.writeln(path.path);
133+
}
134+
}
135+
136+
Directory _determineSdkRoot() {
137+
final Directory sdkRoot;
138+
if (Platform.isMacOS) {
139+
sdkRoot = Directory(
140+
join(Platform.environment['HOME']!, 'Library', 'Android', 'Sdk'));
141+
} else if (Platform.isLinux) {
142+
sdkRoot = Directory(join(Platform.environment['HOME']!, 'Android', 'Sdk'));
143+
} else if (Platform.isWindows) {
144+
sdkRoot = Directory(
145+
join(Platform.environment['LOCALAPPDATA']!, 'Android', 'Sdk'));
146+
} else {
147+
throw const CmdlineToolsInstallUnsupportedPlatformException();
148+
}
149+
sdkRoot.createSync(recursive: true);
150+
return sdkRoot;
151+
}
152+
153+
String _unitMessageFormatter(String message, {required bool isError}) =>
154+
message;

0 commit comments

Comments
 (0)