Skip to content

Commit

Permalink
feat(datastore): Migrate to Amplify Swift V2 (#4962)
Browse files Browse the repository at this point in the history
  • Loading branch information
Equartey committed Jun 4, 2024
1 parent e903fa6 commit 0ff33b3
Show file tree
Hide file tree
Showing 726 changed files with 49,275 additions and 649 deletions.
4 changes: 4 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
## Platform files generated by Flutter during `flutter create`
**/example/android/** linguist-generated
**/example/ios/** linguist-generated
**/example/ios/unit_tests/** linguist-generated=false
**/example/linux/** linguist-generated
**/example/macos/** linguist-generated
**/example/windows/** linguist-generated
Expand All @@ -71,6 +72,9 @@
## Generated SDK files
packages/**/lib/src/sdk/src/** linguist-generated

## Generated Swift Plugins
packages/amplify_datastore/ios/internal/** linguist-generated

## Smithy files
packages/smithy/goldens/lib/** linguist-generated
packages/smithy/goldens/lib2/** linguist-generated
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,317 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import 'dart:async';
import 'dart:io';

import 'package:aft/aft.dart';
import 'package:aft/src/options/glob_options.dart';
import 'package:async/async.dart';
import 'package:git/git.dart';
import 'package:io/io.dart';
import 'package:path/path.dart' as p;

class PluginConfig {
const PluginConfig({
required this.name,
required this.remotePathToSource,
});

/// The name of the plugin.
final String name;

/// The path to the plugin source files in the Amplify Swift repo.
final String remotePathToSource;
}

/// Command for generating the Amplify Swift plugins for the DataStore plugin.
class GenerateAmplifySwiftCommand extends AmplifyCommand with GlobOptions {
GenerateAmplifySwiftCommand() {
argParser
..addOption(
'branch',
abbr: 'b',
help: 'The branch of Amplify Swift to target',
defaultsTo: 'release',
)
..addFlag(
'diff',
abbr: 'd',
help: 'Show the diff of the generated files',
negatable: false,
defaultsTo: false,
);
}

@override
String get description =>
'Generates Amplify Swift DataStore for the DataStore plugin.';

@override
String get name => 'amplify-swift';

@override
bool get hidden => true;

/// The branch of Amplify Swift to target.
///
/// If not provided, defaults to `release`.
late final branchTarget = argResults!['branch'] as String;

late final _dataStoreRootDir =
p.join(rootDir.path, 'packages/amplify_datastore');

final _pluginOutputDir = 'ios/internal';
final _exampleOutputDir = 'example/ios';

/// Whether to check the diff of the generated files.
/// If not provided, defaults to `false`.
late final isDiff = argResults!['diff'] as bool;

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

final _amplifySwiftPlugins = [
const PluginConfig(
name: 'AWSDataStorePlugin',
remotePathToSource: 'AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin',
),
const PluginConfig(
name: 'AWSPluginsCore',
remotePathToSource: 'AmplifyPlugins/Core/AWSPluginsCore',
),
const PluginConfig(
name: 'Amplify',
remotePathToSource: 'Amplify',
),
];

final _importsToRemove = [
'import Amplify',
'import AWSPluginsCore',
'import AWSDataStorePlugin',
];

/// Downloads Amplify Swift from GitHub into a temporary directory.
Future<Directory> _downloadRepository() => _cloneMemo.runOnce(() async {
final cloneDir =
await Directory.systemTemp.createTemp('amplify_swift_');
logger
..info('Downloading Amplify Swift...')
..verbose('Cloning repo to ${cloneDir.path}');
await runGit(
[
'clone',
// https://github.blog/2020-12-21-get-up-to-speed-with-partial-clone-and-shallow-clone/
'--filter=tree:0',
'https://github.com/aws-amplify/amplify-swift.git',
cloneDir.path,
],
echoOutput: verbose,
);
logger.info('Successfully cloned Amplify Swift Repo');
return cloneDir;
});

/// Checks out [ref] in [pluginDir].
Future<Directory> _checkoutRepositoryRef(
Directory pluginDir,
String ref,
) async {
logger
..info('Checking out target branch: $ref')
..verbose('Creating git work tree in $pluginDir');
final worktreeDir =
await Directory.systemTemp.createTemp('amplify_swift_worktree_');
try {
await runGit(
['worktree', 'add', worktreeDir.path, ref],
processWorkingDir: pluginDir.path,
);
} on Exception catch (e) {
if (e.toString().contains('already checked out')) {
return pluginDir;
}
rethrow;
}
return worktreeDir;
}

/// Find and replaces the `import` statements in the plugin files.
Future<void> _replaceImports(Directory pluginDir) async {
final files = await pluginDir.list(recursive: true).toList();
for (final file in files) {
if (file is! File) {
continue;
}
// Only process Swift files.
if (!file.path.endsWith('.swift')) {
continue;
}
final contents = await file.readAsString();
// remove the list of import statement for Amplify including line breaks
final newContents = contents.split('\n').where((line) {
return !_importsToRemove.any((import) => line.contains(import));
}).join('\n');
await file.writeAsString(newContents);
}
}

/// Remove `info.plist` from the plugin files.
Future<void> _removePListFiles(Directory pluginDir) async {
final files = await pluginDir.list(recursive: true).toList();
for (final file in files) {
if (file is! File) {
continue;
}
// Only process Info.plist files.
if (!file.path.endsWith('Info.plist')) {
continue;
}
await file.delete();
}
}

/// Transforms the plugin files to Amplify Flutter requirements.
Future<void> _transformPlugin(Directory directory) async {
logger
..info('Transforming plugin files...')
..verbose('In ${directory.path}');
await _replaceImports(directory);
await _removePListFiles(directory);
}

/// Sets up the Amplify Swift repo for use later
Future<void> _setupRepo() async {
if (_repoCache[branchTarget] != null) {
return;
}
final repoDir = await _downloadRepository();
final repoRef = await _checkoutRepositoryRef(repoDir, branchTarget);

_repoCache[branchTarget] = repoRef;
}

/// Returns the directory for the plugin at [path].
Future<Directory> _pluginDirForPath(String path) async {
final repoDir = _repoCache[branchTarget];
if (repoDir == null) {
exitError('No cached repo for branch $branchTarget');
}

final pluginDir = Directory.fromUri(
repoDir.uri.resolve(path),
);

await _transformPlugin(pluginDir);

return pluginDir;
}

/// Generates the Amplify Swift plugin for [plugin].
Future<void> _generatePlugin(PluginConfig plugin) async {
logger.info('Selecting source files for ${plugin.name}...');

// The directory in the Amplify Swift repo where the plugin source files are.
final remotePluginDir = await _pluginDirForPath(plugin.remotePathToSource);

// The local directory to copy the plugin files to.
final outputDir = Directory(
p.join(_dataStoreRootDir, _pluginOutputDir, plugin.name),
);

// Clear out the directory if it already exists.
// This is to ensure that we don't have any stale files.
if (await outputDir.exists()) {
logger.info(
'Deleting existing plugin directory for ${plugin.name}...',
);
await outputDir.delete(recursive: true);
}
await outputDir.create(recursive: true);

// Copy the files from the repo to the plugin directory.
logger
..info('Copying plugin files for ${plugin.name}...')
..verbose('From $remotePluginDir to $outputDir');
await copyPath(remotePluginDir.path, outputDir.path);
}

Future<void> checkDiff(PluginConfig plugin) async {
logger.info('Checking diff for ${plugin.name}...');
final incoming = (await _pluginDirForPath(plugin.remotePathToSource)).path;
final current = p.join(_dataStoreRootDir, _pluginOutputDir, plugin.name);
final diffCmd = await Process.start(
'git',
[
'diff',
'--no-index',
'--exit-code',
incoming,
current,
],
mode: verbose ? ProcessStartMode.inheritStdio : ProcessStartMode.normal,
);
final exitCode = await diffCmd.exitCode;
if (exitCode != 0) {
exitError(
'`diff` failed: $exitCode. There are differences between $incoming and $current',
);
}
logger.info(
'No differences between incoming and current for ${plugin.name}.',
);
}

/// Runs pod install after copying files to the plugin directory.
Future<void> _podInstall() async {
final podFilePath = p.join(_dataStoreRootDir, _exampleOutputDir);
logger.verbose('Running pod install in $podFilePath...');

final podInstallCmd = await Process.start(
'pod',
[
'install',
],
mode: verbose ? ProcessStartMode.inheritStdio : ProcessStartMode.normal,
workingDirectory: podFilePath,
);
final exitCode = await podInstallCmd.exitCode;
if (exitCode != 0) {
exitError('`pod install` failed: $exitCode.');
}
}

Future<void> _runDiff() async {
await _setupRepo();
for (final plugin in _amplifySwiftPlugins) {
await checkDiff(plugin);
}
logger.info(
'Successfully checked diff for Amplify Swift plugins',
);
}

Future<void> _runGenerate() async {
await _setupRepo();
for (final plugin in _amplifySwiftPlugins) {
await _generatePlugin(plugin);
}
await _podInstall();
logger.info('Successfully generated Amplify Swift plugins');
}

@override
Future<void> run() async {
logger.info('Generating Amplify Swift plugins.');
await super.run();
switch (isDiff) {
case true:
await _runDiff();
default:
await _runGenerate();
break;
}
}
}
2 changes: 2 additions & 0 deletions packages/aft/lib/src/commands/generate/generate_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

import 'package:aft/aft.dart';
import 'package:aft/src/commands/generate/generate_amplify_swift_command.dart';
import 'package:aft/src/commands/generate/generate_goldens_command.dart';
import 'package:aft/src/commands/generate/generate_sdk_command.dart';
import 'package:aft/src/commands/generate/generate_workflows_command.dart';
Expand All @@ -12,6 +13,7 @@ class GenerateCommand extends AmplifyCommand {
addSubcommand(GenerateSdkCommand());
addSubcommand(GenerateWorkflowsCommand());
addSubcommand(GenerateGoldensCommand());
addSubcommand(GenerateAmplifySwiftCommand());
}

@override
Expand Down
1 change: 1 addition & 0 deletions packages/aft/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ dependencies:
git: any # override
glob: ^2.1.0
graphs: ^2.1.0
io: ^1.0.4
json_annotation: ">=4.8.1 <4.9.0"
markdown: ^5.0.0
mason: ^0.1.0-dev.40
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import com.amazonaws.amplify.amplify_datastore.pigeons.NativeApiPlugin
import com.amazonaws.amplify.amplify_datastore.pigeons.NativeAuthBridge
import com.amazonaws.amplify.amplify_datastore.pigeons.NativeAuthPlugin
import com.amazonaws.amplify.amplify_datastore.pigeons.NativeAuthUser
import com.amazonaws.amplify.amplify_datastore.pigeons.NativeGraphQLSubscriptionResponse
import com.amazonaws.amplify.amplify_datastore.types.model.FlutterCustomTypeSchema
import com.amazonaws.amplify.amplify_datastore.types.model.FlutterModelSchema
import com.amazonaws.amplify.amplify_datastore.types.model.FlutterSerializedModel
Expand Down Expand Up @@ -900,6 +901,7 @@ class AmplifyDataStorePlugin :

override fun addApiPlugin(
authProvidersList: List<String>,
endpoints: Map<String, String>,
callback: (kotlin.Result<Unit>) -> Unit
) {
try {
Expand All @@ -920,6 +922,13 @@ class AmplifyDataStorePlugin :
callback(kotlin.Result.failure(e))
}
}

override fun sendSubscriptionEvent(
event: NativeGraphQLSubscriptionResponse,
callback: (kotlin.Result<Unit>) -> Unit
) {
throw NotImplementedError("Not yet implemented")
}

override fun configure(
version: String,
Expand Down
Loading

0 comments on commit 0ff33b3

Please sign in to comment.