Skip to content

Commit

Permalink
chore(aft): Improve SDK generation command
Browse files Browse the repository at this point in the history
Makes `aft generate sdk` behave more like other commands where it can be run with no arguments for all packages and now works with `--include`/`--exclude` as well.
  • Loading branch information
Dillon Nys authored and dnys1 committed Jun 28, 2023
1 parent 821af9f commit 68ac22c
Showing 1 changed file with 95 additions and 51 deletions.
146 changes: 95 additions & 51 deletions packages/aft/lib/src/commands/generate/generate_sdk_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import 'dart:convert';
import 'dart:io';

import 'package:aft/aft.dart';
import 'package:aft/src/options/glob_options.dart';
import 'package:async/async.dart';
import 'package:checked_yaml/checked_yaml.dart';
import 'package:collection/collection.dart';
import 'package:git/git.dart';
Expand All @@ -16,15 +18,9 @@ import 'package:smithy_codegen/smithy_codegen.dart';
import 'package:stream_transform/stream_transform.dart';

/// Command for generating the AWS SDK for a given package and `sdk.yaml` file.
class GenerateSdkCommand extends AmplifyCommand {
class GenerateSdkCommand extends AmplifyCommand with GlobOptions {
GenerateSdkCommand() {
argParser
..addOption(
'config',
abbr: 'c',
defaultsTo: 'sdk.yaml',
help: 'The SDK configuration file',
)
..addOption(
'models',
abbr: 'm',
Expand Down Expand Up @@ -53,23 +49,55 @@ class GenerateSdkCommand extends AmplifyCommand {
@override
bool get hidden => true;

/// The path to local Smithy models, if passed.
late final modelsPath = argResults!['models'] as String?;

/// The lib/-relative output path for the generated SDK.
late final outputPath = argResults!['output'] as String;

/// The package name to use in the generated SDK.
///
/// If not provided, it will be read from the `pubspec.yaml`.
late final packageName = argResults!['package'] as String?;

/// Cache of organized models by git ref.
final _modelsCache = <String, Directory>{};
final _cloneMemo = AsyncMemoizer<Directory>();

/// Downloads AWS models from GitHub into a temporary directory.
Future<Directory> _downloadModels(String ref) async {
final cloneDir = await Directory.systemTemp.createTemp('models');
logger
..info('Downloading models...')
..verbose('Cloning models to ${cloneDir.path}');
await runGit([
'clone',
'https://github.com/aws/aws-models.git',
cloneDir.path,
]);
await runGit(
['checkout', ref],
processWorkingDir: cloneDir.path,
);
logger.info('Successfully cloned models');
return cloneDir;
Future<Directory> _downloadModels() => _cloneMemo.runOnce(() async {
final cloneDir = await Directory.systemTemp.createTemp('models');
logger
..info('Downloading models...')
..verbose('Cloning models to ${cloneDir.path}');
await runGit(
[
'clone',
'https://github.com/aws/aws-models.git',
cloneDir.path,
],
echoOutput: verbose,
);
logger.info('Successfully cloned models');
return cloneDir;
});

/// Checks out [ref] in [modelsDir].
Future<Directory> _checkoutModelsRef(Directory modelsDir, String ref) async {
final worktreeDir =
await Directory.systemTemp.createTemp('models_worktree_');
try {
await runGit(
['worktree', 'add', worktreeDir.path, ref],
processWorkingDir: modelsDir.path,
);
} on Exception catch (e) {
if (e.toString().contains('already checked out')) {
return modelsDir;
}
rethrow;
}
return worktreeDir;
}

/// Organizes model files from [baseDir] into a new temporary directory.
Expand All @@ -96,40 +124,46 @@ class GenerateSdkCommand extends AmplifyCommand {
return modelsDir;
}

@override
Future<void> run() async {
await super.run();
final args = argResults!;
final configFilepath = args['config'] as String;
final configFile = File(configFilepath);
if (!await configFile.exists()) {
exitError('Config file ($configFilepath) does not exist');
Future<Directory> _modelsDirForRef(String ref) async {
if (_modelsCache[ref] case final cachedDir?) {
return cachedDir;
}

final configYaml = await configFile.readAsString();
final config = checkedYamlDecode(configYaml, SdkConfig.fromJson);
logger.verbose('Got config: $config');

final modelsPath = args['models'] as String?;
final Directory modelsDir;
if (modelsPath != null) {
modelsDir = await _organizeModels(Directory(modelsPath));
Directory modelsDir;
if (modelsPath case final modelsPath?) {
modelsDir = Directory(modelsPath);
if (!await modelsDir.exists()) {
exitError('Model directory ($modelsDir) does not exist');
}
modelsDir = await _checkoutModelsRef(modelsDir, ref);
modelsDir = await _organizeModels(modelsDir);
} else {
final cloneDir = await _downloadModels(config.ref);
modelsDir = await _organizeModels(cloneDir);
modelsDir = await _downloadModels();
modelsDir = await _checkoutModelsRef(modelsDir, ref);
modelsDir = await _organizeModels(modelsDir);
}
return _modelsCache[ref] = modelsDir;
}

final outputPath = args['output'] as String;
final outputDir = Directory(p.join('lib', outputPath));
/// Generates the SDK for [package].
Future<void> _generateForPackage(PackageInfo package) async {
final configFile = File(p.join(package.path, 'sdk.yaml'));
if (!await configFile.exists()) {
return;
}

logger.info('Generating SDK for ${package.name}...');
final configYaml = await configFile.readAsString();
final config = checkedYamlDecode(configYaml, SdkConfig.fromJson);
logger.verbose('Got config: $config');

final outputDir = Directory(p.join(package.path, 'lib', outputPath));
if (!await outputDir.exists()) {
await outputDir.create(recursive: true);
}

final smithyModel = SmithyAstBuilder();

final modelsDir = await _modelsDirForRef(config.ref);
final models = modelsDir.list().whereType<File>();
await for (final model in models) {
final serviceName = p.basenameWithoutExtension(model.path);
Expand All @@ -144,10 +178,10 @@ class GenerateSdkCommand extends AmplifyCommand {
}

final smithyAst = smithyModel.build();
var packageName = args['package'] as String?;
var packageName = this.packageName;
if (packageName == null) {
// Read package name from pubspec.yaml
final pubspecFile = File('pubspec.yaml');
final pubspecFile = File(p.join(package.path, 'pubspec.yaml'));
final pubspecYaml = await pubspecFile.readAsString();
final pubspec = Pubspec.parse(pubspecYaml, sourceUrl: pubspecFile.uri);
packageName = pubspec.name;
Expand Down Expand Up @@ -183,6 +217,7 @@ class GenerateSdkCommand extends AmplifyCommand {
for (final library in output.values.expand((out) => out.libraries)) {
final smithyLibrary = library.smithyLibrary;
final outPath = p.join(
package.path,
'lib',
smithyLibrary.libRelativePath,
);
Expand Down Expand Up @@ -213,6 +248,7 @@ class GenerateSdkCommand extends AmplifyCommand {
jsonEncode(generatedAst),
],
mode: ProcessStartMode.inheritStdio,
workingDirectory: package.path,
);
final exitCode = await pluginCmd.exitCode;
if (exitCode != 0) {
Expand All @@ -221,6 +257,7 @@ class GenerateSdkCommand extends AmplifyCommand {
}

// Run built_value generator
logger.info('Running build_runner...');
final buildRunnerCmd = await Process.start(
'dart',
[
Expand All @@ -230,17 +267,24 @@ class GenerateSdkCommand extends AmplifyCommand {
'--delete-conflicting-outputs',
],
mode: verbose ? ProcessStartMode.inheritStdio : ProcessStartMode.normal,
workingDirectory: package.path,
);
final exitCode = await buildRunnerCmd.exitCode;
if (exitCode != 0) {
exitError('`dart run build_runner build` failed: $exitCode.');
}
}

logger
..info('Successfully generated SDK')
..verbose('Make sure to add the following dependencies:');
for (final dep in dependencies.toList()..sort()) {
logger.verbose('- $dep');
@override
Future<void> run() async {
await super.run();
if (commandPackages.isEmpty) {
logger.info('Nothing to generate');
return;
}
for (final package in commandPackages.values) {
await _generateForPackage(package);
}
logger.info('Successfully generated SDK');
}
}

0 comments on commit 68ac22c

Please sign in to comment.