ios_content_validation_test.dart 6.49 KB
Newer Older
1 2 3 4 5 6 7 8
// 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.

import 'dart:io';

import 'package:flutter_devicelab/framework/apk_utils.dart';
import 'package:flutter_devicelab/framework/framework.dart';
9
import 'package:flutter_devicelab/framework/ios.dart';
10
import 'package:flutter_devicelab/framework/task_result.dart';
11 12 13 14 15 16 17
import 'package:flutter_devicelab/framework/utils.dart';
import 'package:path/path.dart' as path;

Future<void> main() async {
  await task(() async {
    try {
      await runProjectTest((FlutterProject flutterProject) async {
18
        section('Build app with with --obfuscate');
19 20 21 22 23 24
        await inDirectory(flutterProject.rootPath, () async {
          await flutter('build', options: <String>[
            'ios',
            '--release',
            '--obfuscate',
            '--split-debug-info=foo/',
25
            '--no-codesign',
26 27
          ]);
        });
28
        final String buildPath = path.join(
29
          flutterProject.rootPath,
30 31 32
          'build',
          'ios',
          'iphoneos',
33
        );
34 35 36
        final String outputAppPath = path.join(
          buildPath,
          'Runner.app',
37
        );
38 39 40 41 42 43 44 45 46 47 48 49 50
        final Directory outputAppFramework = Directory(path.join(
          outputAppPath,
          'Frameworks',
          'App.framework',
        ));

        final File outputAppFrameworkBinary = File(path.join(
          outputAppFramework.path,
          'App',
        ));

        if (!outputAppFrameworkBinary.existsSync()) {
          fail('Failed to produce expected output at ${outputAppFrameworkBinary.path}');
51 52
        }

53 54 55 56 57 58 59
        if (await dartObservatoryBonjourServiceFound(outputAppPath)) {
          throw TaskResult.failure('Release bundle has unexpected NSBonjourServices');
        }
        if (await localNetworkUsageFound(outputAppPath)) {
          throw TaskResult.failure('Release bundle has unexpected NSLocalNetworkUsageDescription');
        }

60 61
        section('Validate obfuscation');

62 63 64 65 66
        // Verify that an identifier from the Dart project code is not present
        // in the compiled binary.
        await inDirectory(flutterProject.rootPath, () async {
          final String response = await eval(
            'grep',
67
            <String>[flutterProject.name, outputAppFrameworkBinary.path],
68 69 70
            canFail: true,
          );
          if (response.trim().contains('matches')) {
71
            throw TaskResult.failure('Found project name in obfuscated dart library');
72 73
          }
        });
74

75
        section('Validate release contents');
76

77
        final Directory outputFlutterFramework = Directory(path.join(
78 79
          flutterProject.rootPath,
          outputAppPath,
80 81 82 83 84 85 86 87 88 89 90
          'Frameworks',
          'Flutter.framework',
        ));
        final File outputFlutterFrameworkBinary = File(path.join(
          outputFlutterFramework.path,
          'Flutter',
        ));

        if (!outputFlutterFrameworkBinary.existsSync()) {
          fail('Failed to produce expected output at ${outputFlutterFrameworkBinary.path}');
        }
91 92 93 94 95 96 97

        // Archiving should contain a bitcode blob, but not building in release.
        // This mimics Xcode behavior and present a developer from having to install a
        // 300+MB app to test devices.
        if (await containsBitcode(outputFlutterFrameworkBinary.path)) {
          throw TaskResult.failure('Bitcode present in Flutter.framework');
        }
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114

        section('Xcode backend script');

        outputFlutterFramework.deleteSync(recursive: true);
        outputAppFramework.deleteSync(recursive: true);
        if (outputFlutterFramework.existsSync() || outputAppFramework.existsSync()) {
          fail('Failed to delete embedded frameworks');
        }

        final String xcodeBackendPath = path.join(
          flutterDirectory.path,
          'packages',
          'flutter_tools',
          'bin',
          'xcode_backend.sh'
        );

115
        // Simulate a common Xcode build setting misconfiguration
116 117 118 119 120 121 122 123 124
        // where FLUTTER_APPLICATION_PATH is missing
        final int result = await exec(
          xcodeBackendPath,
          <String>['embed_and_thin'],
          environment: <String, String>{
            'SOURCE_ROOT': flutterProject.iosPath,
            'TARGET_BUILD_DIR': buildPath,
            'FRAMEWORKS_FOLDER_PATH': 'Runner.app/Frameworks',
            'VERBOSE_SCRIPT_LOGGING': '1',
125
            'FLUTTER_BUILD_MODE': 'release',
126 127
            'ACTION': 'install', // Skip bitcode stripping since we just checked that above.
          },
128 129
        );

130 131 132 133 134 135 136 137 138 139
        if (result != 0) {
          fail('xcode_backend embed_and_thin failed');
        }

        if (!outputFlutterFrameworkBinary.existsSync()) {
          fail('Failed to re-embed ${outputFlutterFrameworkBinary.path}');
        }

        if (!outputAppFrameworkBinary.existsSync()) {
          fail('Failed to re-embed ${outputAppFrameworkBinary.path}');
140
        }
141

142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
        section('Clean build');

        await inDirectory(flutterProject.rootPath, () async {
          await flutter('clean');
        });

        section('Validate debug contents');

        await inDirectory(flutterProject.rootPath, () async {
          await flutter('build', options: <String>[
            'ios',
            '--debug',
            '--no-codesign',
          ]);
        });

        // Debug should also not contain bitcode.
        if (await containsBitcode(outputFlutterFrameworkBinary.path)) {
          throw TaskResult.failure('Bitcode present in Flutter.framework');
        }

        if (!await dartObservatoryBonjourServiceFound(outputAppPath)) {
          throw TaskResult.failure('Debug bundle is missing NSBonjourServices');
        }
        if (!await localNetworkUsageFound(outputAppPath)) {
          throw TaskResult.failure('Debug bundle is missing NSLocalNetworkUsageDescription');
        }
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191

        section('Clean build');

        await inDirectory(flutterProject.rootPath, () async {
          await flutter('clean');
        });

        section('Archive');

        await inDirectory(flutterProject.rootPath, () async {
          await flutter('build', options: <String>[
            'xcarchive',
          ]);
        });

        checkDirectoryExists(path.join(
          flutterProject.rootPath,
          'build',
          'ios',
          'archive',
          'Runner.xcarchive',
          'Products',
        ));
192
      });
193

194 195 196 197 198 199 200 201
      return TaskResult.success(null);
    } on TaskResult catch (taskResult) {
      return taskResult;
    } catch (e) {
      return TaskResult.failure(e.toString());
    }
  });
}