Skip to content

Commit 76cf1cc

Browse files
authored
feat(datastore): Migrate to Amplify Swift V2 (#4962)
1 parent 6944287 commit 76cf1cc

File tree

726 files changed

+49275
-649
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

726 files changed

+49275
-649
lines changed

.gitattributes

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
## Platform files generated by Flutter during `flutter create`
5151
**/example/android/** linguist-generated
5252
**/example/ios/** linguist-generated
53+
**/example/ios/unit_tests/** linguist-generated=false
5354
**/example/linux/** linguist-generated
5455
**/example/macos/** linguist-generated
5556
**/example/windows/** linguist-generated
@@ -71,6 +72,9 @@
7172
## Generated SDK files
7273
packages/**/lib/src/sdk/src/** linguist-generated
7374

75+
## Generated Swift Plugins
76+
packages/amplify_datastore/ios/internal/** linguist-generated
77+
7478
## Smithy files
7579
packages/smithy/goldens/lib/** linguist-generated
7680
packages/smithy/goldens/lib2/** linguist-generated
Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import 'dart:async';
5+
import 'dart:io';
6+
7+
import 'package:aft/aft.dart';
8+
import 'package:aft/src/options/glob_options.dart';
9+
import 'package:async/async.dart';
10+
import 'package:git/git.dart';
11+
import 'package:io/io.dart';
12+
import 'package:path/path.dart' as p;
13+
14+
class PluginConfig {
15+
const PluginConfig({
16+
required this.name,
17+
required this.remotePathToSource,
18+
});
19+
20+
/// The name of the plugin.
21+
final String name;
22+
23+
/// The path to the plugin source files in the Amplify Swift repo.
24+
final String remotePathToSource;
25+
}
26+
27+
/// Command for generating the Amplify Swift plugins for the DataStore plugin.
28+
class GenerateAmplifySwiftCommand extends AmplifyCommand with GlobOptions {
29+
GenerateAmplifySwiftCommand() {
30+
argParser
31+
..addOption(
32+
'branch',
33+
abbr: 'b',
34+
help: 'The branch of Amplify Swift to target',
35+
defaultsTo: 'release',
36+
)
37+
..addFlag(
38+
'diff',
39+
abbr: 'd',
40+
help: 'Show the diff of the generated files',
41+
negatable: false,
42+
defaultsTo: false,
43+
);
44+
}
45+
46+
@override
47+
String get description =>
48+
'Generates Amplify Swift DataStore for the DataStore plugin.';
49+
50+
@override
51+
String get name => 'amplify-swift';
52+
53+
@override
54+
bool get hidden => true;
55+
56+
/// The branch of Amplify Swift to target.
57+
///
58+
/// If not provided, defaults to `release`.
59+
late final branchTarget = argResults!['branch'] as String;
60+
61+
late final _dataStoreRootDir =
62+
p.join(rootDir.path, 'packages/amplify_datastore');
63+
64+
final _pluginOutputDir = 'ios/internal';
65+
final _exampleOutputDir = 'example/ios';
66+
67+
/// Whether to check the diff of the generated files.
68+
/// If not provided, defaults to `false`.
69+
late final isDiff = argResults!['diff'] as bool;
70+
71+
/// Cache of repos by git ref.
72+
final _repoCache = <String, Directory>{};
73+
final _cloneMemo = AsyncMemoizer<Directory>();
74+
75+
final _amplifySwiftPlugins = [
76+
const PluginConfig(
77+
name: 'AWSDataStorePlugin',
78+
remotePathToSource: 'AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin',
79+
),
80+
const PluginConfig(
81+
name: 'AWSPluginsCore',
82+
remotePathToSource: 'AmplifyPlugins/Core/AWSPluginsCore',
83+
),
84+
const PluginConfig(
85+
name: 'Amplify',
86+
remotePathToSource: 'Amplify',
87+
),
88+
];
89+
90+
final _importsToRemove = [
91+
'import Amplify',
92+
'import AWSPluginsCore',
93+
'import AWSDataStorePlugin',
94+
];
95+
96+
/// Downloads Amplify Swift from GitHub into a temporary directory.
97+
Future<Directory> _downloadRepository() => _cloneMemo.runOnce(() async {
98+
final cloneDir =
99+
await Directory.systemTemp.createTemp('amplify_swift_');
100+
logger
101+
..info('Downloading Amplify Swift...')
102+
..verbose('Cloning repo to ${cloneDir.path}');
103+
await runGit(
104+
[
105+
'clone',
106+
// https://github.blog/2020-12-21-get-up-to-speed-with-partial-clone-and-shallow-clone/
107+
'--filter=tree:0',
108+
'https://github.com/aws-amplify/amplify-swift.git',
109+
cloneDir.path,
110+
],
111+
echoOutput: verbose,
112+
);
113+
logger.info('Successfully cloned Amplify Swift Repo');
114+
return cloneDir;
115+
});
116+
117+
/// Checks out [ref] in [pluginDir].
118+
Future<Directory> _checkoutRepositoryRef(
119+
Directory pluginDir,
120+
String ref,
121+
) async {
122+
logger
123+
..info('Checking out target branch: $ref')
124+
..verbose('Creating git work tree in $pluginDir');
125+
final worktreeDir =
126+
await Directory.systemTemp.createTemp('amplify_swift_worktree_');
127+
try {
128+
await runGit(
129+
['worktree', 'add', worktreeDir.path, ref],
130+
processWorkingDir: pluginDir.path,
131+
);
132+
} on Exception catch (e) {
133+
if (e.toString().contains('already checked out')) {
134+
return pluginDir;
135+
}
136+
rethrow;
137+
}
138+
return worktreeDir;
139+
}
140+
141+
/// Find and replaces the `import` statements in the plugin files.
142+
Future<void> _replaceImports(Directory pluginDir) async {
143+
final files = await pluginDir.list(recursive: true).toList();
144+
for (final file in files) {
145+
if (file is! File) {
146+
continue;
147+
}
148+
// Only process Swift files.
149+
if (!file.path.endsWith('.swift')) {
150+
continue;
151+
}
152+
final contents = await file.readAsString();
153+
// remove the list of import statement for Amplify including line breaks
154+
final newContents = contents.split('\n').where((line) {
155+
return !_importsToRemove.any((import) => line.contains(import));
156+
}).join('\n');
157+
await file.writeAsString(newContents);
158+
}
159+
}
160+
161+
/// Remove `info.plist` from the plugin files.
162+
Future<void> _removePListFiles(Directory pluginDir) async {
163+
final files = await pluginDir.list(recursive: true).toList();
164+
for (final file in files) {
165+
if (file is! File) {
166+
continue;
167+
}
168+
// Only process Info.plist files.
169+
if (!file.path.endsWith('Info.plist')) {
170+
continue;
171+
}
172+
await file.delete();
173+
}
174+
}
175+
176+
/// Transforms the plugin files to Amplify Flutter requirements.
177+
Future<void> _transformPlugin(Directory directory) async {
178+
logger
179+
..info('Transforming plugin files...')
180+
..verbose('In ${directory.path}');
181+
await _replaceImports(directory);
182+
await _removePListFiles(directory);
183+
}
184+
185+
/// Sets up the Amplify Swift repo for use later
186+
Future<void> _setupRepo() async {
187+
if (_repoCache[branchTarget] != null) {
188+
return;
189+
}
190+
final repoDir = await _downloadRepository();
191+
final repoRef = await _checkoutRepositoryRef(repoDir, branchTarget);
192+
193+
_repoCache[branchTarget] = repoRef;
194+
}
195+
196+
/// Returns the directory for the plugin at [path].
197+
Future<Directory> _pluginDirForPath(String path) async {
198+
final repoDir = _repoCache[branchTarget];
199+
if (repoDir == null) {
200+
exitError('No cached repo for branch $branchTarget');
201+
}
202+
203+
final pluginDir = Directory.fromUri(
204+
repoDir.uri.resolve(path),
205+
);
206+
207+
await _transformPlugin(pluginDir);
208+
209+
return pluginDir;
210+
}
211+
212+
/// Generates the Amplify Swift plugin for [plugin].
213+
Future<void> _generatePlugin(PluginConfig plugin) async {
214+
logger.info('Selecting source files for ${plugin.name}...');
215+
216+
// The directory in the Amplify Swift repo where the plugin source files are.
217+
final remotePluginDir = await _pluginDirForPath(plugin.remotePathToSource);
218+
219+
// The local directory to copy the plugin files to.
220+
final outputDir = Directory(
221+
p.join(_dataStoreRootDir, _pluginOutputDir, plugin.name),
222+
);
223+
224+
// Clear out the directory if it already exists.
225+
// This is to ensure that we don't have any stale files.
226+
if (await outputDir.exists()) {
227+
logger.info(
228+
'Deleting existing plugin directory for ${plugin.name}...',
229+
);
230+
await outputDir.delete(recursive: true);
231+
}
232+
await outputDir.create(recursive: true);
233+
234+
// Copy the files from the repo to the plugin directory.
235+
logger
236+
..info('Copying plugin files for ${plugin.name}...')
237+
..verbose('From $remotePluginDir to $outputDir');
238+
await copyPath(remotePluginDir.path, outputDir.path);
239+
}
240+
241+
Future<void> checkDiff(PluginConfig plugin) async {
242+
logger.info('Checking diff for ${plugin.name}...');
243+
final incoming = (await _pluginDirForPath(plugin.remotePathToSource)).path;
244+
final current = p.join(_dataStoreRootDir, _pluginOutputDir, plugin.name);
245+
final diffCmd = await Process.start(
246+
'git',
247+
[
248+
'diff',
249+
'--no-index',
250+
'--exit-code',
251+
incoming,
252+
current,
253+
],
254+
mode: verbose ? ProcessStartMode.inheritStdio : ProcessStartMode.normal,
255+
);
256+
final exitCode = await diffCmd.exitCode;
257+
if (exitCode != 0) {
258+
exitError(
259+
'`diff` failed: $exitCode. There are differences between $incoming and $current',
260+
);
261+
}
262+
logger.info(
263+
'No differences between incoming and current for ${plugin.name}.',
264+
);
265+
}
266+
267+
/// Runs pod install after copying files to the plugin directory.
268+
Future<void> _podInstall() async {
269+
final podFilePath = p.join(_dataStoreRootDir, _exampleOutputDir);
270+
logger.verbose('Running pod install in $podFilePath...');
271+
272+
final podInstallCmd = await Process.start(
273+
'pod',
274+
[
275+
'install',
276+
],
277+
mode: verbose ? ProcessStartMode.inheritStdio : ProcessStartMode.normal,
278+
workingDirectory: podFilePath,
279+
);
280+
final exitCode = await podInstallCmd.exitCode;
281+
if (exitCode != 0) {
282+
exitError('`pod install` failed: $exitCode.');
283+
}
284+
}
285+
286+
Future<void> _runDiff() async {
287+
await _setupRepo();
288+
for (final plugin in _amplifySwiftPlugins) {
289+
await checkDiff(plugin);
290+
}
291+
logger.info(
292+
'Successfully checked diff for Amplify Swift plugins',
293+
);
294+
}
295+
296+
Future<void> _runGenerate() async {
297+
await _setupRepo();
298+
for (final plugin in _amplifySwiftPlugins) {
299+
await _generatePlugin(plugin);
300+
}
301+
await _podInstall();
302+
logger.info('Successfully generated Amplify Swift plugins');
303+
}
304+
305+
@override
306+
Future<void> run() async {
307+
logger.info('Generating Amplify Swift plugins.');
308+
await super.run();
309+
switch (isDiff) {
310+
case true:
311+
await _runDiff();
312+
default:
313+
await _runGenerate();
314+
break;
315+
}
316+
}
317+
}

packages/aft/lib/src/commands/generate/generate_command.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
import 'package:aft/aft.dart';
5+
import 'package:aft/src/commands/generate/generate_amplify_swift_command.dart';
56
import 'package:aft/src/commands/generate/generate_goldens_command.dart';
67
import 'package:aft/src/commands/generate/generate_sdk_command.dart';
78
import 'package:aft/src/commands/generate/generate_workflows_command.dart';
@@ -12,6 +13,7 @@ class GenerateCommand extends AmplifyCommand {
1213
addSubcommand(GenerateSdkCommand());
1314
addSubcommand(GenerateWorkflowsCommand());
1415
addSubcommand(GenerateGoldensCommand());
16+
addSubcommand(GenerateAmplifySwiftCommand());
1517
}
1618

1719
@override

packages/aft/pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ dependencies:
2020
git: any # override
2121
glob: ^2.1.0
2222
graphs: ^2.1.0
23+
io: ^1.0.4
2324
json_annotation: ">=4.8.1 <4.9.0"
2425
markdown: ^5.0.0
2526
mason: ^0.1.0-dev.40

packages/amplify_datastore/android/src/main/kotlin/com/amazonaws/amplify/amplify_datastore/AmplifyDataStorePlugin.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import com.amazonaws.amplify.amplify_datastore.pigeons.NativeApiPlugin
1919
import com.amazonaws.amplify.amplify_datastore.pigeons.NativeAuthBridge
2020
import com.amazonaws.amplify.amplify_datastore.pigeons.NativeAuthPlugin
2121
import com.amazonaws.amplify.amplify_datastore.pigeons.NativeAuthUser
22+
import com.amazonaws.amplify.amplify_datastore.pigeons.NativeGraphQLSubscriptionResponse
2223
import com.amazonaws.amplify.amplify_datastore.types.model.FlutterCustomTypeSchema
2324
import com.amazonaws.amplify.amplify_datastore.types.model.FlutterModelSchema
2425
import com.amazonaws.amplify.amplify_datastore.types.model.FlutterSerializedModel
@@ -900,6 +901,7 @@ class AmplifyDataStorePlugin :
900901

901902
override fun addApiPlugin(
902903
authProvidersList: List<String>,
904+
endpoints: Map<String, String>,
903905
callback: (kotlin.Result<Unit>) -> Unit
904906
) {
905907
try {
@@ -920,6 +922,13 @@ class AmplifyDataStorePlugin :
920922
callback(kotlin.Result.failure(e))
921923
}
922924
}
925+
926+
override fun sendSubscriptionEvent(
927+
event: NativeGraphQLSubscriptionResponse,
928+
callback: (kotlin.Result<Unit>) -> Unit
929+
) {
930+
throw NotImplementedError("Not yet implemented")
931+
}
923932

924933
override fun configure(
925934
version: String,

0 commit comments

Comments
 (0)