-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add updater_tools with package_patch command (#163)
* Initial updater_tools buildout * cont'd * update readme * use cli arg name variables * coverage * pr feedback * coverage * comments * Add libapp.so hash to patch package
- Loading branch information
1 parent
5707bd8
commit 43b21ee
Showing
21 changed files
with
1,214 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,4 @@ | ||
include: package:very_good_analysis/analysis_options.5.1.0.yaml | ||
analyzer: | ||
exclude: | ||
- lib/version.dart |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import 'dart:io'; | ||
|
||
import 'package:scoped_deps/scoped_deps.dart'; | ||
import 'package:updater_tools/src/logger.dart'; | ||
import 'package:updater_tools/src/process.dart'; | ||
import 'package:updater_tools/src/updater_tools_command_runner.dart'; | ||
|
||
Future<void> main(List<String> args) async { | ||
await _flushThenExit( | ||
await runScoped( | ||
() async => UpdaterToolsCommandRunner().run(args), | ||
values: { | ||
loggerRef, | ||
processManagerRef, | ||
}, | ||
), | ||
); | ||
} | ||
|
||
/// Flushes the stdout and stderr streams, then exits the program with the given | ||
/// status code. | ||
/// | ||
/// This returns a Future that will never complete, since the program will have | ||
/// exited already. This is useful to prevent Future chains from proceeding | ||
/// after you've decided to exit. | ||
Future<void> _flushThenExit(int status) { | ||
return Future.wait<void>([stdout.close(), stderr.close()]) | ||
.then<void>((_) => exit(status)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
/// {@template archive_type} | ||
/// The type of archive we are creating a patch for. | ||
/// {@endtemplate} | ||
enum ArchiveType { | ||
/// Android App Bundle | ||
aab, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export 'package_patch_command.dart'; |
141 changes: 141 additions & 0 deletions
141
updater_tools/lib/src/commands/package_patch_command.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
import 'dart:io'; | ||
|
||
import 'package:mason_logger/mason_logger.dart'; | ||
import 'package:updater_tools/src/artifact_type.dart'; | ||
import 'package:updater_tools/src/commands/updater_tool_command.dart'; | ||
import 'package:updater_tools/src/logger.dart'; | ||
import 'package:updater_tools/src/packager/patch_packager.dart'; | ||
|
||
/// The arg name to specify the release and patch archive type. | ||
const archiveTypeCliArg = 'archive-type'; | ||
|
||
/// The arg name to specify the path to the release archive. | ||
const releaseCliArg = 'release'; | ||
|
||
/// The arg name to specify the path to the patch archive. | ||
const patchCliArg = 'patch'; | ||
|
||
/// The arg name to specify the path to the patch executable. | ||
const patchExecutableCliArg = 'patch-executable'; | ||
|
||
/// The arg name to specify the output directory. | ||
const outputCliArg = 'output'; | ||
|
||
/// Function signature for the [PatchPackager] constructor. | ||
typedef MakePatchPackager = PatchPackager Function({ | ||
required File patchExecutable, | ||
}); | ||
|
||
/// {@template package_patch_command} | ||
/// A command to package patch artifacts. | ||
/// {@endtemplate} | ||
class PackagePatchCommand extends UpdaterToolCommand { | ||
/// {@macro package_patch_command} | ||
PackagePatchCommand([MakePatchPackager? makePatchPackager]) | ||
: _makePatchPackagerOverride = makePatchPackager, | ||
super() { | ||
argParser | ||
..addOption( | ||
archiveTypeCliArg, | ||
help: 'The format of release and patch. These *must* be the same.', | ||
allowed: ArchiveType.values.asNameMap().keys, | ||
mandatory: true, | ||
) | ||
..addOption( | ||
releaseCliArg, | ||
abbr: 'r', | ||
mandatory: true, | ||
help: 'The path to the release artifact which will be patched', | ||
) | ||
..addOption( | ||
patchCliArg, | ||
abbr: 'p', | ||
mandatory: true, | ||
help: 'The path to the patch artifact which will be packaged', | ||
) | ||
..addOption( | ||
patchExecutableCliArg, | ||
mandatory: true, | ||
help: | ||
'''The path to the patch executable that creates a binary diff between two files''', | ||
) | ||
..addOption( | ||
outputCliArg, | ||
abbr: 'o', | ||
mandatory: true, | ||
help: ''' | ||
Where to write the packaged patch archives. | ||
This should be a directory, and will contain patch archives for each architecture.''', | ||
); | ||
} | ||
|
||
final MakePatchPackager? _makePatchPackagerOverride; | ||
|
||
@override | ||
String get description => | ||
'''A command that turns two app archives (.aab, .xcarchive, etc.) into patch artifacts.'''; | ||
|
||
@override | ||
String get name => 'package_patch'; | ||
|
||
@override | ||
Future<int> run() async { | ||
final releaseFile = File(results[releaseCliArg] as String); | ||
final patchFile = File(results[patchCliArg] as String); | ||
final patchExecutable = File(results[patchExecutableCliArg] as String); | ||
final outputDirectory = Directory(results[outputCliArg] as String); | ||
final archiveType = ArchiveType.values.byName( | ||
results[archiveTypeCliArg] as String, | ||
); | ||
|
||
try { | ||
_assertCliArgsValid(); | ||
} catch (e) { | ||
logger.err('$e'); | ||
return ExitCode.usage.code; | ||
} | ||
|
||
if (outputDirectory.existsSync()) { | ||
logger.info('${outputDirectory.path} already exists. Deleting...'); | ||
outputDirectory.deleteSync(recursive: true); | ||
} | ||
|
||
final packager = (_makePatchPackagerOverride ?? PatchPackager.new)( | ||
patchExecutable: patchExecutable, | ||
); | ||
await packager.packagePatch( | ||
releaseArchive: releaseFile, | ||
patchArchive: patchFile, | ||
archiveType: archiveType, | ||
outputDirectory: outputDirectory, | ||
); | ||
|
||
logger.info('Patch packaged to ${outputDirectory.path}'); | ||
|
||
return ExitCode.success.code; | ||
} | ||
|
||
/// Verifies that CLI arguments point to existing files. Throws an | ||
/// [ArgumentError] if any of the args are not valid. | ||
void _assertCliArgsValid() { | ||
final releaseFilePath = results[releaseCliArg] as String; | ||
final patchFilePath = results[patchCliArg] as String; | ||
final patchExecutablePath = results[patchExecutableCliArg] as String; | ||
|
||
_verifyFileExists(releaseFilePath, releaseCliArg); | ||
_verifyFileExists(patchFilePath, patchCliArg); | ||
_verifyFileExists(patchExecutablePath, patchExecutableCliArg); | ||
} | ||
|
||
/// Throws an [ArgumentError] if a file at [path] does not exist. | ||
void _verifyFileExists(String path, String name) { | ||
if (!File(path).existsSync()) { | ||
throw ArgumentError.value( | ||
path, | ||
name, | ||
'The $name file does not exist', | ||
); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import 'package:args/args.dart'; | ||
import 'package:args/command_runner.dart'; | ||
import 'package:meta/meta.dart'; | ||
|
||
/// {@template updater_tool_command} | ||
/// A base class for updater tool commands. | ||
/// {@endtemplate} | ||
abstract class UpdaterToolCommand extends Command<int> { | ||
/// [ArgResults] used for testing purposes only. | ||
@visibleForTesting | ||
ArgResults? testArgResults; | ||
|
||
/// [ArgResults] for the current command. | ||
ArgResults get results => testArgResults ?? argResults!; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import 'dart:io'; | ||
import 'dart:isolate'; | ||
|
||
import 'package:archive/archive_io.dart'; | ||
import 'package:path/path.dart' as p; | ||
|
||
/// Functions for archiving directories. | ||
extension DirectoryArchive on Directory { | ||
/// Copies this directory to a temporary directory and zips it. | ||
Future<File> zipToTempFile() async { | ||
final tempDir = await Directory.systemTemp.createTemp(); | ||
final outFile = File(p.join(tempDir.path, '${p.basename(path)}.zip')); | ||
await Isolate.run(() { | ||
ZipFileEncoder().zipDirectory(this, filename: outFile.path); | ||
}); | ||
return outFile; | ||
} | ||
} | ||
|
||
/// Functions for unarchiving files. | ||
extension FileArchive on File { | ||
/// Extracts this zip file to the [outputDirectory] directory in a separate | ||
/// isolate. | ||
Future<void> extractZip({required Directory outputDirectory}) async { | ||
await Isolate.run(() async { | ||
final inputStream = InputFileStream(path); | ||
final archive = ZipDecoder().decodeBuffer(inputStream); | ||
await extractArchiveToDisk(archive, outputDirectory.path); | ||
inputStream.closeSync(); | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export 'archive.dart'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import 'package:mason_logger/mason_logger.dart'; | ||
import 'package:scoped_deps/scoped_deps.dart'; | ||
|
||
/// A reference to a [Logger] instance. | ||
final loggerRef = create(Logger.new); | ||
|
||
/// The [Logger] instance available in the current zone. | ||
Logger get logger => read(loggerRef); |
Oops, something went wrong.