ios_project_migration_test.dart 18.8 KB
Newer Older
1 2 3 4
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
// @dart = 2.8

7 8 9
import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/logger.dart';
10
import 'package:flutter_tools/src/base/project_migrator.dart';
11
import 'package:flutter_tools/src/ios/migrations/project_base_configuration_migration.dart';
12
import 'package:flutter_tools/src/ios/migrations/project_build_location_migration.dart';
13
import 'package:flutter_tools/src/ios/migrations/remove_framework_link_and_embedding_migration.dart';
14
import 'package:flutter_tools/src/ios/migrations/xcode_build_system_migration.dart';
15
import 'package:flutter_tools/src/project.dart';
16
import 'package:flutter_tools/src/reporting/reporting.dart';
17 18
import 'package:meta/meta.dart';
import 'package:mockito/mockito.dart';
19 20 21 22

import '../../src/common.dart';

void main () {
23
  group('iOS migration', () {
24
    TestUsage testUsage;
25
    setUp(() {
26
      testUsage = TestUsage();
27 28
    });

29 30
    testWithoutContext('migrators succeed', () {
      final FakeIOSMigrator fakeIOSMigrator = FakeIOSMigrator(succeeds: true);
31
      final ProjectMigration migration = ProjectMigration(<ProjectMigrator>[fakeIOSMigrator]);
32
      expect(migration.run(), isTrue);
33 34
    });

35 36
    testWithoutContext('migrators fail', () {
      final FakeIOSMigrator fakeIOSMigrator = FakeIOSMigrator(succeeds: false);
37
      final ProjectMigration migration = ProjectMigration(<ProjectMigrator>[fakeIOSMigrator]);
38
      expect(migration.run(), isFalse);
39 40
    });

41 42 43 44 45 46 47
    group('remove framework linking and embedding migration', () {
      MemoryFileSystem memoryFileSystem;
      BufferLogger testLogger;
      MockIosProject mockIosProject;
      File xcodeProjectInfoFile;

      setUp(() {
48
        memoryFileSystem = MemoryFileSystem.test();
49
        xcodeProjectInfoFile = memoryFileSystem.file('project.pbxproj');
50
        testLogger = BufferLogger.test();
51 52 53 54 55 56 57 58
        mockIosProject = MockIosProject();
        when(mockIosProject.xcodeProjectInfoFile).thenReturn(xcodeProjectInfoFile);
      });

      testWithoutContext('skipped if files are missing', () {
        final RemoveFrameworkLinkAndEmbeddingMigration iosProjectMigration = RemoveFrameworkLinkAndEmbeddingMigration(
          mockIosProject,
          testLogger,
59
          testUsage
60 61
        );
        expect(iosProjectMigration.migrate(), isTrue);
62
        expect(testUsage.events, isEmpty);
63 64 65

        expect(xcodeProjectInfoFile.existsSync(), isFalse);

66
        expect(testLogger.traceText, contains('Xcode project not found, skipping framework link and embedding migration'));
67 68 69 70 71 72 73 74 75 76 77
        expect(testLogger.statusText, isEmpty);
      });

      testWithoutContext('skipped if nothing to upgrade', () {
        const String contents = 'Nothing to upgrade';
        xcodeProjectInfoFile.writeAsStringSync(contents);
        final DateTime projectLastModified = xcodeProjectInfoFile.lastModifiedSync();

        final RemoveFrameworkLinkAndEmbeddingMigration iosProjectMigration = RemoveFrameworkLinkAndEmbeddingMigration(
          mockIosProject,
          testLogger,
78
          testUsage,
79 80
        );
        expect(iosProjectMigration.migrate(), isTrue);
81
        expect(testUsage.events, isEmpty);
82 83 84 85 86 87 88 89

        expect(xcodeProjectInfoFile.lastModifiedSync(), projectLastModified);
        expect(xcodeProjectInfoFile.readAsStringSync(), contents);

        expect(testLogger.statusText, isEmpty);
      });

      testWithoutContext('skips migrating script with embed', () {
90 91
        const String contents = r'''
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed\n/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
92
			''';
93 94 95 96 97
        xcodeProjectInfoFile.writeAsStringSync(contents);

        final RemoveFrameworkLinkAndEmbeddingMigration iosProjectMigration = RemoveFrameworkLinkAndEmbeddingMigration(
          mockIosProject,
          testLogger,
98
          testUsage,
99 100 101 102 103 104 105
        );
        expect(iosProjectMigration.migrate(), isTrue);
        expect(xcodeProjectInfoFile.readAsStringSync(), contents);
        expect(testLogger.statusText, isEmpty);
      });

      testWithoutContext('Xcode project is migrated', () {
106
        xcodeProjectInfoFile.writeAsStringSync(r'''
107 108 109 110 111 112 113 114 115 116 117
prefix 3B80C3941E831B6300D905FE
3B80C3951E831B6300D905FE suffix
741F496821356857001E2961
keep this 1
  3B80C3931E831B6300D905FE spaces
741F496521356807001E2961
9705A1C61CF904A100538489
9705A1C71CF904A300538489
741F496221355F47001E2961
9740EEBA1CF902C7004384FC
741F495E21355F27001E2961
118
			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
119 120 121
keep this 2
''');

122 123 124
        final RemoveFrameworkLinkAndEmbeddingMigration iosProjectMigration = RemoveFrameworkLinkAndEmbeddingMigration(
          mockIosProject,
          testLogger,
125
          testUsage,
126 127
        );
        expect(iosProjectMigration.migrate(), isTrue);
128
        expect(testUsage.events, isEmpty);
129

130
        expect(xcodeProjectInfoFile.readAsStringSync(), r'''
131
keep this 1
132
			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
133 134
keep this 2
''');
135 136
        expect(testLogger.statusText, contains('Upgrading project.pbxproj'));
      });
137

138 139
      testWithoutContext('migration fails with leftover App.framework reference', () {
        xcodeProjectInfoFile.writeAsStringSync('''
140 141
        746232531E83B71900CC1A5E /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 746232521E83B71900CC1A5E /* App.framework */; };
''');
142 143 144 145

        final RemoveFrameworkLinkAndEmbeddingMigration iosProjectMigration = RemoveFrameworkLinkAndEmbeddingMigration(
          mockIosProject,
          testLogger,
146
          testUsage,
147 148
        );

149
        expect(iosProjectMigration.migrate, throwsToolExit(message: 'Your Xcode project requires migration'));
150 151 152
        expect(testUsage.events, contains(
          const TestUsageEvent('ios-migration', 'remove-frameworks', label: 'failure'),
        ));
153 154 155 156
      });

      testWithoutContext('migration fails with leftover Flutter.framework reference', () {
        xcodeProjectInfoFile.writeAsStringSync('''
157 158
      9705A1C71CF904A300538480 /* Flutter.framework in Embed Frameworks */,
''');
159 160 161 162

        final RemoveFrameworkLinkAndEmbeddingMigration iosProjectMigration = RemoveFrameworkLinkAndEmbeddingMigration(
          mockIosProject,
          testLogger,
163
          testUsage,
164
        );
165
        expect(iosProjectMigration.migrate, throwsToolExit(message: 'Your Xcode project requires migration'));
166 167 168
        expect(testUsage.events, contains(
          const TestUsageEvent('ios-migration', 'remove-frameworks', label: 'failure'),
        ));
169 170 171 172
      });

      testWithoutContext('migration fails without Xcode installed', () {
        xcodeProjectInfoFile.writeAsStringSync('''
173 174
        746232531E83B71900CC1A5E /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 746232521E83B71900CC1A5E /* App.framework */; };
''');
175 176 177 178

        final RemoveFrameworkLinkAndEmbeddingMigration iosProjectMigration = RemoveFrameworkLinkAndEmbeddingMigration(
          mockIosProject,
          testLogger,
179
          testUsage,
180
        );
181
        expect(iosProjectMigration.migrate, throwsToolExit(message: 'Your Xcode project requires migration'));
182 183 184
        expect(testUsage.events, contains(
          const TestUsageEvent('ios-migration', 'remove-frameworks', label: 'failure'),
        ));
185
      });
186
    });
187 188 189 190 191 192 193 194

    group('new Xcode build system', () {
      MemoryFileSystem memoryFileSystem;
      BufferLogger testLogger;
      MockIosProject mockIosProject;
      File xcodeWorkspaceSharedSettings;

      setUp(() {
195
        memoryFileSystem = MemoryFileSystem.test();
196
        xcodeWorkspaceSharedSettings = memoryFileSystem.file('WorkspaceSettings.xcsettings');
197
        testLogger = BufferLogger.test();
198 199 200 201 202 203 204 205 206 207 208 209
        mockIosProject = MockIosProject();
        when(mockIosProject.xcodeWorkspaceSharedSettings).thenReturn(xcodeWorkspaceSharedSettings);
      });

      testWithoutContext('skipped if files are missing', () {
        final XcodeBuildSystemMigration iosProjectMigration = XcodeBuildSystemMigration(
          mockIosProject,
          testLogger,
        );
        expect(iosProjectMigration.migrate(), isTrue);
        expect(xcodeWorkspaceSharedSettings.existsSync(), isFalse);

210
        expect(testLogger.traceText, contains('Xcode workspace settings not found, skipping build system migration'));
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
        expect(testLogger.statusText, isEmpty);
      });

      testWithoutContext('skipped if nothing to upgrade', () {
        const String contents = '''
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>BuildSystemType</key>
	<string></string>
</dict>
</plist>''';
        xcodeWorkspaceSharedSettings.writeAsStringSync(contents);

        final XcodeBuildSystemMigration iosProjectMigration = XcodeBuildSystemMigration(
          mockIosProject,
          testLogger,
        );
        expect(iosProjectMigration.migrate(), isTrue);
        expect(xcodeWorkspaceSharedSettings.existsSync(), isTrue);
        expect(testLogger.statusText, isEmpty);
      });

      testWithoutContext('Xcode project is migrated', () {
        const String contents = '''
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>BuildSystemType</key>
	<string>Original</string>
243 244
	<key>PreviewsEnabled</key>
	<false/>
245 246 247 248 249 250 251 252 253 254 255 256 257 258
</dict>
</plist>''';
        xcodeWorkspaceSharedSettings.writeAsStringSync(contents);

        final XcodeBuildSystemMigration iosProjectMigration = XcodeBuildSystemMigration(
          mockIosProject,
          testLogger,
        );
        expect(iosProjectMigration.migrate(), isTrue);
        expect(xcodeWorkspaceSharedSettings.existsSync(), isFalse);

        expect(testLogger.statusText, contains('Legacy build system detected, removing'));
      });
    });
259

260
    group('Xcode default build location', () {
261 262 263
      MemoryFileSystem memoryFileSystem;
      BufferLogger testLogger;
      MockIosProject mockIosProject;
264
      File xcodeProjectWorkspaceData;
265 266

      setUp(() {
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304
        memoryFileSystem = MemoryFileSystem();
        xcodeProjectWorkspaceData = memoryFileSystem.file('contents.xcworkspacedata');
        testLogger = BufferLogger.test();
        mockIosProject = MockIosProject();
        when(mockIosProject.xcodeProjectWorkspaceData).thenReturn(xcodeProjectWorkspaceData);
      });

      testWithoutContext('skipped if files are missing', () {
        final ProjectBuildLocationMigration iosProjectMigration = ProjectBuildLocationMigration(
          mockIosProject,
          testLogger,
        );
        expect(iosProjectMigration.migrate(), isTrue);
        expect(xcodeProjectWorkspaceData.existsSync(), isFalse);

        expect(testLogger.traceText, contains('Xcode project workspace data not found, skipping build location migration.'));
        expect(testLogger.statusText, isEmpty);
      });

      testWithoutContext('skipped if nothing to upgrade', () {
        const String contents = '''
 <?xml version="1.0" encoding="UTF-8"?>
 <Workspace
    version = "1.0">
    <FileRef
      location = "self:">
    </FileRef>
 </Workspace>''';
        xcodeProjectWorkspaceData.writeAsStringSync(contents);

        final ProjectBuildLocationMigration iosProjectMigration = ProjectBuildLocationMigration(
          mockIosProject,
          testLogger,
        );
        expect(iosProjectMigration.migrate(), isTrue);
        expect(xcodeProjectWorkspaceData.existsSync(), isTrue);
        expect(testLogger.statusText, isEmpty);
      });
305

306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323
      testWithoutContext('Xcode project is migrated', () {
        const String contents = '''
 <?xml version="1.0" encoding="UTF-8"?>
 <Workspace
   version = "1.0">
   <FileRef
      location = "group:Runner.xcodeproj">
   </FileRef>
   <FileRef
      location = "group:Pods/Pods.xcodeproj">
   </FileRef>
 </Workspace>
''';
        xcodeProjectWorkspaceData.writeAsStringSync(contents);

        final ProjectBuildLocationMigration iosProjectMigration = ProjectBuildLocationMigration(
          mockIosProject,
          testLogger,
324
        );
325 326 327 328 329 330 331 332 333 334 335 336 337
        expect(iosProjectMigration.migrate(), isTrue);
        expect(xcodeProjectWorkspaceData.readAsStringSync(), '''
 <?xml version="1.0" encoding="UTF-8"?>
 <Workspace
   version = "1.0">
   <FileRef
      location = "self:">
   </FileRef>
 </Workspace>
''');
        expect(testLogger.statusText, contains('Upgrading contents.xcworkspacedata'));
      });
    });
338

339 340 341 342 343 344 345 346 347 348
    group('remove Runner project base configuration', () {
      MemoryFileSystem memoryFileSystem;
      BufferLogger testLogger;
      MockIosProject mockIosProject;
      File xcodeProjectInfoFile;

      setUp(() {
        memoryFileSystem = MemoryFileSystem();
        xcodeProjectInfoFile = memoryFileSystem.file('project.pbxproj');
        testLogger = BufferLogger.test();
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491
        mockIosProject = MockIosProject();
        when(mockIosProject.xcodeProjectInfoFile).thenReturn(xcodeProjectInfoFile);
      });

      testWithoutContext('skipped if files are missing', () {
        final ProjectBaseConfigurationMigration iosProjectMigration = ProjectBaseConfigurationMigration(
          mockIosProject,
          testLogger,
        );
        expect(iosProjectMigration.migrate(), isTrue);
        expect(xcodeProjectInfoFile.existsSync(), isFalse);

        expect(testLogger.traceText, contains('Xcode project not found, skipping Runner project build settings and configuration migration'));
        expect(testLogger.statusText, isEmpty);
      });

      testWithoutContext('skipped if nothing to upgrade', () {
        const String contents = 'Nothing to upgrade';
        xcodeProjectInfoFile.writeAsStringSync(contents);
        final DateTime projectLastModified = xcodeProjectInfoFile.lastModifiedSync();

        final ProjectBaseConfigurationMigration iosProjectMigration = ProjectBaseConfigurationMigration(
          mockIosProject,
          testLogger,
        );
        expect(iosProjectMigration.migrate(), isTrue);

        expect(xcodeProjectInfoFile.lastModifiedSync(), projectLastModified);
        expect(xcodeProjectInfoFile.readAsStringSync(), contents);

        expect(testLogger.statusText, isEmpty);
      });

      testWithoutContext('Xcode project is migrated with template identifiers', () {
        xcodeProjectInfoFile.writeAsStringSync('''
		97C147031CF9000F007C117D /* Debug */ = {
			isa = XCBuildConfiguration;
			baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
keep this 1
		249021D3217E4FDB00AE95B9 /* Profile */ = {
			isa = XCBuildConfiguration;
			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
keep this 2
		97C147041CF9000F007C117D /* Release */ = {
			isa = XCBuildConfiguration;
			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
keep this 3
''');

        final ProjectBaseConfigurationMigration iosProjectMigration = ProjectBaseConfigurationMigration(
          mockIosProject,
          testLogger,
        );
        expect(iosProjectMigration.migrate(), isTrue);

        expect(xcodeProjectInfoFile.readAsStringSync(), '''
		97C147031CF9000F007C117D /* Debug */ = {
			isa = XCBuildConfiguration;
keep this 1
		249021D3217E4FDB00AE95B9 /* Profile */ = {
			isa = XCBuildConfiguration;
keep this 2
		97C147041CF9000F007C117D /* Release */ = {
			isa = XCBuildConfiguration;
keep this 3
''');
        expect(testLogger.statusText, contains('Project base configurations detected, removing.'));
      });

      testWithoutContext('Xcode project is migrated with custom identifiers', () {
        xcodeProjectInfoFile.writeAsStringSync('''
		97C147031CF9000F007C1171 /* Debug */ = {
			isa = XCBuildConfiguration;
			baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
		2436755321828D23008C7051 /* Profile */ = {
			isa = XCBuildConfiguration;
			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
		97C147041CF9000F007C1171 /* Release */ = {
			isa = XCBuildConfiguration;
			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
      /* Begin XCConfigurationList section */
      97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
        isa = XCConfigurationList;
        buildConfigurations = (
          97C147031CF9000F007C1171 /* Debug */,
          97C147041CF9000F007C1171 /* Release */,
          2436755321828D23008C7051 /* Profile */,
        );
        defaultConfigurationIsVisible = 0;
        defaultConfigurationName = Release;
      };
      97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
        isa = XCConfigurationList;
        buildConfigurations = (
          97C147061CF9000F007C117D /* Debug */,
          97C147071CF9000F007C117D /* Release */,
          2436755421828D23008C705F /* Profile */,
        );
        defaultConfigurationIsVisible = 0;
        defaultConfigurationName = Release;
      };
/* End XCConfigurationList section */
''');

        final ProjectBaseConfigurationMigration iosProjectMigration = ProjectBaseConfigurationMigration(
          mockIosProject,
          testLogger,
        );
        expect(iosProjectMigration.migrate(), isTrue);

        expect(xcodeProjectInfoFile.readAsStringSync(), '''
		97C147031CF9000F007C1171 /* Debug */ = {
			isa = XCBuildConfiguration;
		2436755321828D23008C7051 /* Profile */ = {
			isa = XCBuildConfiguration;
		97C147041CF9000F007C1171 /* Release */ = {
			isa = XCBuildConfiguration;
      /* Begin XCConfigurationList section */
      97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
        isa = XCConfigurationList;
        buildConfigurations = (
          97C147031CF9000F007C1171 /* Debug */,
          97C147041CF9000F007C1171 /* Release */,
          2436755321828D23008C7051 /* Profile */,
        );
        defaultConfigurationIsVisible = 0;
        defaultConfigurationName = Release;
      };
      97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
        isa = XCConfigurationList;
        buildConfigurations = (
          97C147061CF9000F007C117D /* Debug */,
          97C147071CF9000F007C117D /* Release */,
          2436755421828D23008C705F /* Profile */,
        );
        defaultConfigurationIsVisible = 0;
        defaultConfigurationName = Release;
      };
/* End XCConfigurationList section */
''');
        expect(testLogger.statusText, contains('Project base configurations detected, removing.'));
      });
    });
492 493 494 495
  });
}

class MockIosProject extends Mock implements IosProject {}
496

497
class FakeIOSMigrator extends ProjectMigrator {
498 499 500 501 502 503 504 505 506 507 508 509 510 511 512
  FakeIOSMigrator({@required this.succeeds})
    : super(null);

  final bool succeeds;

  @override
  bool migrate() {
    return succeeds;
  }

  @override
  String migrateLine(String line) {
    return line;
  }
}