build_linux_test.dart 28 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'package:args/command_runner.dart';
6
import 'package:file/memory.dart';
7
import 'package:file_testing/file_testing.dart';
8
import 'package:flutter_tools/src/artifacts.dart';
9
import 'package:flutter_tools/src/base/file_system.dart';
10
import 'package:flutter_tools/src/base/logger.dart';
11
import 'package:flutter_tools/src/base/os.dart';
12
import 'package:flutter_tools/src/base/platform.dart';
13
import 'package:flutter_tools/src/base/process.dart';
14
import 'package:flutter_tools/src/base/utils.dart';
15
import 'package:flutter_tools/src/build_system/build_system.dart';
16
import 'package:flutter_tools/src/cache.dart';
17
import 'package:flutter_tools/src/cmake.dart';
18
import 'package:flutter_tools/src/commands/build.dart';
19
import 'package:flutter_tools/src/commands/build_linux.dart';
20
import 'package:flutter_tools/src/features.dart';
21
import 'package:flutter_tools/src/project.dart';
22
import 'package:flutter_tools/src/reporting/reporting.dart';
23
import 'package:test/fake.dart';
24

25 26
import '../../src/common.dart';
import '../../src/context.dart';
27
import '../../src/fakes.dart';
28
import '../../src/test_build_system.dart';
29
import '../../src/test_flutter_command_runner.dart';
30

31 32
const String _kTestFlutterRoot = '/flutter';

33 34
final Platform linuxPlatform = FakePlatform(
  environment: <String, String>{
35 36
    'FLUTTER_ROOT': _kTestFlutterRoot,
    'HOME': '/',
37 38 39 40 41
  }
);
final Platform notLinuxPlatform = FakePlatform(
  operatingSystem: 'macos',
  environment: <String, String>{
42
    'FLUTTER_ROOT': _kTestFlutterRoot,
43 44 45 46
  }
);

void main() {
47 48
  setUpAll(() {
    Cache.disableLocking();
49
  });
50

51
  late FileSystem fileSystem;
52 53 54
  late FakeProcessManager processManager;
  late ProcessUtils processUtils;
  late Logger logger;
55
  late TestUsage usage;
56
  late Artifacts artifacts;
57

58
  setUp(() {
59
    fileSystem = MemoryFileSystem.test();
60
    artifacts = Artifacts.test(fileSystem: fileSystem);
61
    Cache.flutterRoot = _kTestFlutterRoot;
62
    usage = TestUsage();
63 64 65 66 67 68
    logger = BufferLogger.test();
    processManager = FakeProcessManager.empty();
    processUtils = ProcessUtils(
      logger: logger,
      processManager: processManager,
    );
69 70
  });

71 72
  // Creates the mock files necessary to look like a Flutter project.
  void setUpMockCoreProjectFiles() {
73 74 75
    fileSystem.file('pubspec.yaml').createSync();
    fileSystem.file('.packages').createSync();
    fileSystem.file(fileSystem.path.join('lib', 'main.dart')).createSync(recursive: true);
76 77
  }

78
  // Creates the mock files necessary to run a build.
79
  void setUpMockProjectFilesForBuild() {
80
    setUpMockCoreProjectFiles();
81
    fileSystem.file(fileSystem.path.join('linux', 'CMakeLists.txt')).createSync(recursive: true);
82 83
  }

84
  // Returns the command matching the build_linux call to cmake.
85 86
  FakeCommand cmakeCommand(String buildMode, {
    String target = 'x64',
87
    void Function()? onRun,
88
  }) {
89 90 91 92 93
    return FakeCommand(
      command: <String>[
        'cmake',
        '-G',
        'Ninja',
94
        '-DCMAKE_BUILD_TYPE=${sentenceCase(buildMode)}',
95
        '-DFLUTTER_TARGET_PLATFORM=linux-$target',
96 97
        '/linux',
      ],
98
      workingDirectory: 'build/linux/$target/$buildMode',
99 100
      onRun: onRun,
    );
101 102
  }

103 104
  // Returns the command matching the build_linux call to ninja.
  FakeCommand ninjaCommand(String buildMode, {
105
    Map<String, String>? environment,
106
    String target = 'x64',
107
    void Function()? onRun,
108 109 110 111 112 113
    String stdout = '',
  }) {
    return FakeCommand(
      command: <String>[
        'ninja',
        '-C',
114
        'build/linux/$target/$buildMode',
115 116 117 118 119 120
        'install',
      ],
      environment: environment,
      onRun: onRun,
      stdout: stdout,
    );
121 122
  }

123
  testUsingContext('Linux build fails when there is no linux project', () async {
124
    final BuildCommand command = BuildCommand(
125
      artifacts: artifacts,
126 127
      androidSdk: FakeAndroidSdk(),
      buildSystem: TestBuildSystem.all(BuildResult(success: true)),
128 129 130
      fileSystem: fileSystem,
      logger: logger,
      processUtils: processUtils,
131 132
      osUtils: FakeOperatingSystemUtils(),
    );
133
    setUpMockCoreProjectFiles();
134

135
    expect(createTestCommandRunner(command).run(
136
      const <String>['build', 'linux', '--no-pub']
137
    ), throwsToolExit(message: 'No Linux desktop project configured. See '
138
      'https://docs.flutter.dev/desktop#add-desktop-support-to-an-existing-flutter-app '
139
      'to learn about adding Linux support to a project.'));
140 141
  }, overrides: <Type, Generator>{
    Platform: () => linuxPlatform,
142
    FileSystem: () => fileSystem,
143
    ProcessManager: () => processManager,
144
    FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
145 146 147
  });

  testUsingContext('Linux build fails on non-linux platform', () async {
148
    final BuildCommand command = BuildCommand(
149
      artifacts: artifacts,
150 151
      androidSdk: FakeAndroidSdk(),
      buildSystem: TestBuildSystem.all(BuildResult(success: true)),
152 153 154
      fileSystem: fileSystem,
      logger: logger,
      processUtils: processUtils,
155 156
      osUtils: FakeOperatingSystemUtils(),
    );
157
    setUpMockProjectFilesForBuild();
158 159

    expect(createTestCommandRunner(command).run(
160
      const <String>['build', 'linux', '--no-pub']
161
    ), throwsToolExit(message: '"build linux" only supported on Linux hosts.'));
162 163
  }, overrides: <Type, Generator>{
    Platform: () => notLinuxPlatform,
164
    FileSystem: () => fileSystem,
165
    ProcessManager: () => processManager,
166
    FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
167 168
  });

169
  testUsingContext('Linux build fails when feature is disabled', () async {
170
    final BuildCommand command = BuildCommand(
171
      artifacts: artifacts,
172 173
      androidSdk: FakeAndroidSdk(),
      buildSystem: TestBuildSystem.all(BuildResult(success: true)),
174 175 176
      fileSystem: fileSystem,
      logger: logger,
      processUtils: processUtils,
177 178
      osUtils: FakeOperatingSystemUtils(),
    );
179 180 181 182 183 184 185 186
    setUpMockProjectFilesForBuild();

    expect(createTestCommandRunner(command).run(
        const <String>['build', 'linux', '--no-pub']
    ), throwsToolExit(message: '"build linux" is not currently supported. To enable, run "flutter config --enable-linux-desktop".'));
  }, overrides: <Type, Generator>{
    Platform: () => linuxPlatform,
    FileSystem: () => fileSystem,
187
    ProcessManager: () => processManager,
188
    FeatureFlags: () => TestFeatureFlags(),
189 190
  });

191
  testUsingContext('Linux build invokes CMake and ninja, and writes temporary files', () async {
192
    final BuildCommand command = BuildCommand(
193
      artifacts: artifacts,
194 195
      androidSdk: FakeAndroidSdk(),
      buildSystem: TestBuildSystem.all(BuildResult(success: true)),
196 197 198
      fileSystem: fileSystem,
      logger: logger,
      processUtils: processUtils,
199 200
      osUtils: FakeOperatingSystemUtils(),
    );
201
    processManager.addCommands(<FakeCommand>[
202 203
      cmakeCommand('release'),
      ninjaCommand('release'),
204 205
    ]);

206
    setUpMockProjectFilesForBuild();
207 208

    await createTestCommandRunner(command).run(
209
      const <String>['build', 'linux', '--no-pub']
210
    );
211
    expect(fileSystem.file('linux/flutter/ephemeral/generated_config.cmake'), exists);
212
  }, overrides: <Type, Generator>{
213 214
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
215
    Platform: () => linuxPlatform,
216
    FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
217
    OperatingSystemUtils: () => FakeOperatingSystemUtils(),
218
  });
219

220
  testUsingContext('Handles missing cmake', () async {
221
    final BuildCommand command = BuildCommand(
222
      artifacts: artifacts,
223 224
      androidSdk: FakeAndroidSdk(),
      buildSystem: TestBuildSystem.all(BuildResult(success: true)),
225 226 227
      fileSystem: fileSystem,
      logger: logger,
      processUtils: processUtils,
228 229
      osUtils: FakeOperatingSystemUtils(),
    );
230
    setUpMockProjectFilesForBuild();
231 232
    processManager = FakeProcessManager.empty()
        ..excludedExecutables.add('cmake');
233 234

    expect(createTestCommandRunner(command).run(
235
      const <String>['build', 'linux', '--no-pub']
236
    ), throwsToolExit(message: 'CMake is required for Linux development.'));
237 238 239 240 241
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
    Platform: () => linuxPlatform,
    FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
242
    OperatingSystemUtils: () => FakeOperatingSystemUtils(),
243 244 245
  });

  testUsingContext('Handles argument error from missing ninja', () async {
246
    final BuildCommand command = BuildCommand(
247
      artifacts: artifacts,
248 249
      androidSdk: FakeAndroidSdk(),
      buildSystem: TestBuildSystem.all(BuildResult(success: true)),
250 251 252
      fileSystem: fileSystem,
      logger: logger,
      processUtils: processUtils,
253 254
      osUtils: FakeOperatingSystemUtils(),
    );
255
    setUpMockProjectFilesForBuild();
256
    processManager.addCommands(<FakeCommand>[
257 258
      cmakeCommand('release'),
      ninjaCommand('release', onRun: () {
259
        throw ArgumentError();
260 261 262 263 264 265
      }),
    ]);

    expect(createTestCommandRunner(command).run(
      const <String>['build', 'linux', '--no-pub']
    ), throwsToolExit(message: "ninja not found. Run 'flutter doctor' for more information."));
266
  }, overrides: <Type, Generator>{
267 268
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
269 270
    Platform: () => linuxPlatform,
    FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
271
    OperatingSystemUtils: () => FakeOperatingSystemUtils(),
272 273
  });

274
  testUsingContext('Linux build does not spew stdout to status logger', () async {
275
    final BuildCommand command = BuildCommand(
276
      artifacts: artifacts,
277 278
      androidSdk: FakeAndroidSdk(),
      buildSystem: TestBuildSystem.all(BuildResult(success: true)),
279 280 281
      fileSystem: fileSystem,
      logger: logger,
      processUtils: processUtils,
282 283
      osUtils: FakeOperatingSystemUtils(),
    );
284
    setUpMockProjectFilesForBuild();
285
    processManager.addCommands(<FakeCommand>[
286 287 288 289
      cmakeCommand('debug'),
      ninjaCommand('debug',
        stdout: 'STDOUT STUFF',
      ),
290
    ]);
291 292

    await createTestCommandRunner(command).run(
293
      const <String>['build', 'linux', '--debug', '--no-pub']
294
    );
295
    expect(testLogger.statusText, isNot(contains('STDOUT STUFF')));
296 297
    expect(testLogger.warningText, isNot(contains('STDOUT STUFF')));
    expect(testLogger.errorText, isNot(contains('STDOUT STUFF')));
298
    expect(testLogger.traceText, contains('STDOUT STUFF'));
299
  }, overrides: <Type, Generator>{
300 301
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
302 303
    Platform: () => linuxPlatform,
    FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
304
    OperatingSystemUtils: () => FakeOperatingSystemUtils(),
305 306
  });

307
  testUsingContext('Linux build extracts errors from stdout', () async {
308
    final BuildCommand command = BuildCommand(
309
      artifacts: artifacts,
310 311
      androidSdk: FakeAndroidSdk(),
      buildSystem: TestBuildSystem.all(BuildResult(success: true)),
312 313 314
      fileSystem: fileSystem,
      logger: logger,
      processUtils: processUtils,
315 316
      osUtils: FakeOperatingSystemUtils(),
    );
317 318 319 320 321
    setUpMockProjectFilesForBuild();

    // This contains a mix of routine build output and various types of errors
    // (Dart error, compile error, link error), edited down for compactness.
    const String stdout = r'''
322
ninja: Entering directory `build/linux/x64/release'
323 324 325 326 327 328 329
[1/6] Generating /foo/linux/flutter/ephemeral/libflutter_linux_gtk.so, /foo/linux/flutter/ephemeral/flutter_linux/flutter_linux.h, _phony
lib/main.dart:4:3: Error: Method not found: 'foo'.
[2/6] Building CXX object CMakeFiles/foo.dir/main.cc.o
/foo/linux/main.cc:6:2: error: expected ';' after class
/foo/linux/main.cc:9:7: warning: unused variable 'unused_variable' [-Wunused-variable]
/foo/linux/main.cc:10:3: error: unknown type name 'UnknownType'
/foo/linux/main.cc:12:7: error: 'bar' is a private member of 'Foo'
330
/foo/linux/my_application.h:4:10: fatal error: 'gtk/gtk.h' file not found
331 332 333 334 335 336
[3/6] Building CXX object CMakeFiles/foo_bar.dir/flutter/generated_plugin_registrant.cc.o
[4/6] Building CXX object CMakeFiles/foo_bar.dir/my_application.cc.o
[5/6] Linking CXX executable intermediates_do_not_run/foo_bar
main.cc:(.text+0x13): undefined reference to `Foo::bar()'
clang: error: linker command failed with exit code 1 (use -v to see invocation)
ninja: build stopped: subcommand failed.
337
ERROR: No file or variants found for asset: images/a_dot_burr.jpeg
338 339
''';

340
    processManager.addCommands(<FakeCommand>[
341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356
      cmakeCommand('release'),
      ninjaCommand('release',
        stdout: stdout,
      ),
    ]);

    await createTestCommandRunner(command).run(
      const <String>['build', 'linux', '--no-pub']
    );
    // Just the warnings and errors should be surfaced.
    expect(testLogger.errorText, r'''
lib/main.dart:4:3: Error: Method not found: 'foo'.
/foo/linux/main.cc:6:2: error: expected ';' after class
/foo/linux/main.cc:9:7: warning: unused variable 'unused_variable' [-Wunused-variable]
/foo/linux/main.cc:10:3: error: unknown type name 'UnknownType'
/foo/linux/main.cc:12:7: error: 'bar' is a private member of 'Foo'
357
/foo/linux/my_application.h:4:10: fatal error: 'gtk/gtk.h' file not found
358
clang: error: linker command failed with exit code 1 (use -v to see invocation)
359
ERROR: No file or variants found for asset: images/a_dot_burr.jpeg
360 361 362 363 364 365
''');
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
    Platform: () => linuxPlatform,
    FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
366
    OperatingSystemUtils: () => FakeOperatingSystemUtils(),
367 368
  });

369
  testUsingContext('Linux verbose build sets VERBOSE_SCRIPT_LOGGING', () async {
370
    final BuildCommand command = BuildCommand(
371
      artifacts: artifacts,
372 373
      androidSdk: FakeAndroidSdk(),
      buildSystem: TestBuildSystem.all(BuildResult(success: true)),
374 375 376
      fileSystem: fileSystem,
      logger: logger,
      processUtils: processUtils,
377 378
      osUtils: FakeOperatingSystemUtils(),
    );
379
    setUpMockProjectFilesForBuild();
380
    processManager.addCommands(<FakeCommand>[
381 382
      cmakeCommand('debug'),
      ninjaCommand('debug',
383
        environment: const <String, String>{
384
          'VERBOSE_SCRIPT_LOGGING': 'true',
385 386 387 388 389 390 391 392 393 394
        },
        stdout: 'STDOUT STUFF',
      ),
    ]);

    await createTestCommandRunner(command).run(
      const <String>['build', 'linux', '--debug', '-v', '--no-pub']
    );
    expect(testLogger.statusText, contains('STDOUT STUFF'));
    expect(testLogger.traceText, isNot(contains('STDOUT STUFF')));
395 396
    expect(testLogger.warningText, isNot(contains('STDOUT STUFF')));
    expect(testLogger.errorText, isNot(contains('STDOUT STUFF')));
397 398
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
399
    Logger: () => logger,
400 401 402
    ProcessManager: () => processManager,
    Platform: () => linuxPlatform,
    FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
403
    OperatingSystemUtils: () => FakeOperatingSystemUtils(),
404 405
  });

406
  testUsingContext('Linux on x64 build --debug passes debug mode to cmake and ninja', () async {
407
    final BuildCommand command = BuildCommand(
408
      artifacts: artifacts,
409 410
      androidSdk: FakeAndroidSdk(),
      buildSystem: TestBuildSystem.all(BuildResult(success: true)),
411 412 413
      fileSystem: fileSystem,
      logger: logger,
      processUtils: processUtils,
414 415
      osUtils: FakeOperatingSystemUtils(),
    );
416
    setUpMockProjectFilesForBuild();
417
    processManager.addCommands(<FakeCommand>[
418 419
      cmakeCommand('debug'),
      ninjaCommand('debug'),
420 421
    ]);

422 423 424 425 426
    await createTestCommandRunner(command).run(
      const <String>['build', 'linux', '--debug', '--no-pub']
    );
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
427
    Logger: () => logger,
428 429 430 431 432 433 434
    ProcessManager: () => processManager,
    Platform: () => linuxPlatform,
    FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
    OperatingSystemUtils: () => FakeOperatingSystemUtils(),
  });

  testUsingContext('Linux on ARM64 build --debug passes debug mode to cmake and ninja', () async {
435
    final BuildCommand command = BuildCommand(
436
      artifacts: artifacts,
437 438
      androidSdk: FakeAndroidSdk(),
      buildSystem: TestBuildSystem.all(BuildResult(success: true)),
439 440 441
      fileSystem: fileSystem,
      logger: logger,
      processUtils: processUtils,
442 443
      osUtils: CustomFakeOperatingSystemUtils(hostPlatform: HostPlatform.linux_arm64),
    );
444
    setUpMockProjectFilesForBuild();
445
    processManager.addCommands(<FakeCommand>[
446 447 448
      cmakeCommand('debug', target: 'arm64'),
      ninjaCommand('debug', target: 'arm64'),
    ]);
449 450

    await createTestCommandRunner(command).run(
451
      const <String>['build', 'linux', '--debug', '--no-pub']
452 453
    );
  }, overrides: <Type, Generator>{
454 455
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
456 457 458 459
    Platform: () => linuxPlatform,
    FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
  });

460
  testUsingContext('Linux on x64 build --profile passes profile mode to make', () async {
461
    final BuildCommand command = BuildCommand(
462
      artifacts: artifacts,
463 464
      androidSdk: FakeAndroidSdk(),
      buildSystem: TestBuildSystem.all(BuildResult(success: true)),
465 466 467
      fileSystem: fileSystem,
      logger: logger,
      processUtils: processUtils,
468 469
      osUtils: FakeOperatingSystemUtils(),
    );
470
    setUpMockProjectFilesForBuild();
471
    processManager.addCommands(<FakeCommand>[
472 473
      cmakeCommand('profile'),
      ninjaCommand('profile'),
474
    ]);
475 476

    await createTestCommandRunner(command).run(
477
      const <String>['build', 'linux', '--profile', '--no-pub']
478 479
    );
  }, overrides: <Type, Generator>{
480 481
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
482 483
    Platform: () => linuxPlatform,
    FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
484 485 486 487
    OperatingSystemUtils: () => FakeOperatingSystemUtils(),
  });

  testUsingContext('Linux on ARM64 build --profile passes profile mode to make', () async {
488
    final BuildCommand command = BuildCommand(
489
      artifacts: artifacts,
490 491
      androidSdk: FakeAndroidSdk(),
      buildSystem: TestBuildSystem.all(BuildResult(success: true)),
492 493 494
      fileSystem: fileSystem,
      logger: logger,
      processUtils: processUtils,
495 496
      osUtils: CustomFakeOperatingSystemUtils(hostPlatform: HostPlatform.linux_arm64),
    );
497
    setUpMockProjectFilesForBuild();
498
    processManager.addCommands(<FakeCommand>[
499 500 501 502 503 504 505 506 507 508 509 510 511 512 513
      cmakeCommand('profile', target: 'arm64'),
      ninjaCommand('profile', target: 'arm64'),
    ]);

    await createTestCommandRunner(command).run(
      const <String>['build', 'linux', '--profile', '--no-pub']
    );
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
    Platform: () => linuxPlatform,
    FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
  });

  testUsingContext('Not support Linux cross-build for x64 on arm64', () async {
514
    final BuildCommand command = BuildCommand(
515
      artifacts: artifacts,
516 517
      androidSdk: FakeAndroidSdk(),
      buildSystem: TestBuildSystem.all(BuildResult(success: true)),
518 519 520
      fileSystem: fileSystem,
      logger: logger,
      processUtils: processUtils,
521 522
      osUtils: CustomFakeOperatingSystemUtils(hostPlatform: HostPlatform.linux_arm64),
    );
523 524 525 526 527 528 529

    expect(createTestCommandRunner(command).run(
      const <String>['build', 'linux', '--no-pub', '--target-platform=linux-x64']
    ), throwsToolExit());
  }, overrides: <Type, Generator>{
    Platform: () => linuxPlatform,
    FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
530 531
  });

532
  testUsingContext('Linux build configures CMake exports', () async {
533
    final BuildCommand command = BuildCommand(
534
      artifacts: artifacts,
535 536
      androidSdk: FakeAndroidSdk(),
      buildSystem: TestBuildSystem.all(BuildResult(success: true)),
537 538 539
      fileSystem: fileSystem,
      logger: logger,
      processUtils: processUtils,
540 541
      osUtils: FakeOperatingSystemUtils(),
    );
542
    setUpMockProjectFilesForBuild();
543
    processManager.addCommands(<FakeCommand>[
544 545
      cmakeCommand('release'),
      ninjaCommand('release'),
546 547 548
    ]);
    fileSystem.file('lib/other.dart')
      .createSync(recursive: true);
549 550
    fileSystem.file('foo/bar.sksl.json')
      .createSync(recursive: true);
551 552 553 554 555 556 557 558 559 560 561 562 563 564

    await createTestCommandRunner(command).run(
      const <String>[
        'build',
        'linux',
        '--target=lib/other.dart',
        '--no-pub',
        '--track-widget-creation',
        '--split-debug-info=foo/',
        '--enable-experiment=non-nullable',
        '--obfuscate',
        '--dart-define=foo.bar=2',
        '--dart-define=fizz.far=3',
        '--tree-shake-icons',
565
        '--bundle-sksl-path=foo/bar.sksl.json',
566 567 568
      ]
    );

569
    final File cmakeConfig = fileSystem.currentDirectory
570 571 572
      .childDirectory('linux')
      .childDirectory('flutter')
      .childDirectory('ephemeral')
573
      .childFile('generated_config.cmake');
574

575
    expect(cmakeConfig, exists);
576

577
    final List<String> configLines = cmakeConfig.readAsLinesSync();
578 579

    expect(configLines, containsAll(<String>[
580 581
      'file(TO_CMAKE_PATH "$_kTestFlutterRoot" FLUTTER_ROOT)',
      'file(TO_CMAKE_PATH "${fileSystem.currentDirectory.path}" PROJECT_DIR)',
582 583 584 585 586
      'set(FLUTTER_VERSION "1.0.0" PARENT_SCOPE)',
      'set(FLUTTER_VERSION_MAJOR 1 PARENT_SCOPE)',
      'set(FLUTTER_VERSION_MINOR 0 PARENT_SCOPE)',
      'set(FLUTTER_VERSION_PATCH 0 PARENT_SCOPE)',
      'set(FLUTTER_VERSION_BUILD 0 PARENT_SCOPE)',
587
      '  "DART_DEFINES=Zm9vLmJhcj0y,Zml6ei5mYXI9Mw=="',
588
      '  "DART_OBFUSCATION=true"',
589 590
      '  "EXTRA_FRONT_END_OPTIONS=--enable-experiment=non-nullable"',
      '  "EXTRA_GEN_SNAPSHOT_OPTIONS=--enable-experiment=non-nullable"',
591 592 593 594 595 596 597
      '  "SPLIT_DEBUG_INFO=foo/"',
      '  "TRACK_WIDGET_CREATION=true"',
      '  "TREE_SHAKE_ICONS=true"',
      '  "FLUTTER_ROOT=$_kTestFlutterRoot"',
      '  "PROJECT_DIR=${fileSystem.currentDirectory.path}"',
      '  "FLUTTER_TARGET=lib/other.dart"',
      '  "BUNDLE_SKSL_PATH=foo/bar.sksl.json"',
598 599 600 601 602 603
    ]));
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
    Platform: () => linuxPlatform,
    FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
604
    OperatingSystemUtils: () => FakeOperatingSystemUtils(),
605 606
  });

607 608
  testUsingContext('linux can extract binary name from CMake file', () async {
    fileSystem.file('linux/CMakeLists.txt')
609 610
      ..createSync(recursive: true)
      ..writeAsStringSync(r'''
611 612
cmake_minimum_required(VERSION 3.10)
project(runner LANGUAGES CXX)
613

614
set(BINARY_NAME "fizz_bar")
615
''');
616 617
    fileSystem.file('pubspec.yaml').createSync();
    fileSystem.file('.packages').createSync();
618
    final FlutterProject flutterProject = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
619

620
    expect(getCmakeExecutableName(flutterProject.linux), 'fizz_bar');
621
  }, overrides: <Type, Generator>{
622
    FileSystem: () => fileSystem,
623
    ProcessManager: () => processManager,
624 625 626 627
    FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
  });

  testUsingContext('Refuses to build for Linux when feature is disabled', () {
628
    final CommandRunner<void> runner = createTestCommandRunner(BuildCommand(
629
      artifacts: artifacts,
630 631
      androidSdk: FakeAndroidSdk(),
      buildSystem: TestBuildSystem.all(BuildResult(success: true)),
632 633 634
      fileSystem: fileSystem,
      logger: logger,
      processUtils: processUtils,
635 636
      osUtils: FakeOperatingSystemUtils(),
    ));
637

638 639 640 641
    expect(
      () => runner.run(<String>['build', 'linux', '--no-pub']),
      throwsToolExit(),
    );
642
  }, overrides: <Type, Generator>{
643
    FeatureFlags: () => TestFeatureFlags(),
644
  });
645

646
  testUsingContext('hidden when not enabled on Linux host', () {
647
    expect(BuildLinuxCommand(logger: BufferLogger.test(), operatingSystemUtils: FakeOperatingSystemUtils()).hidden, true);
648
  }, overrides: <Type, Generator>{
649
    FeatureFlags: () => TestFeatureFlags(),
650
    Platform: () => notLinuxPlatform,
651 652 653
  });

  testUsingContext('Not hidden when enabled and on Linux host', () {
654
    expect(BuildLinuxCommand(logger: BufferLogger.test(), operatingSystemUtils: FakeOperatingSystemUtils()).hidden, false);
655 656
  }, overrides: <Type, Generator>{
    FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
657
    Platform: () => linuxPlatform,
658
  });
659 660

  testUsingContext('Performs code size analysis and sends analytics', () async {
661
    final BuildCommand command = BuildCommand(
662
      artifacts: artifacts,
663 664
      androidSdk: FakeAndroidSdk(),
      buildSystem: TestBuildSystem.all(BuildResult(success: true)),
665 666 667
      fileSystem: fileSystem,
      logger: logger,
      processUtils: processUtils,
668 669
      osUtils: FakeOperatingSystemUtils(),
    );
670
    setUpMockProjectFilesForBuild();
671
    processManager.addCommands(<FakeCommand>[
672 673 674 675
      cmakeCommand('release'),
      ninjaCommand('release', onRun: () {
        fileSystem.file('build/flutter_size_01/snapshot.linux-x64.json')
          ..createSync(recursive: true)
676 677 678 679 680 681 682 683 684
          ..writeAsStringSync('''
[
  {
    "l": "dart:_internal",
    "c": "SubListIterable",
    "n": "[Optimized] skip",
    "s": 2400
  }
]''');
685 686 687 688 689 690
        fileSystem.file('build/flutter_size_01/trace.linux-x64.json')
          ..createSync(recursive: true)
          ..writeAsStringSync('{}');
      }),
    ]);

691
    fileSystem.file('build/linux/x64/release/bundle/libapp.so')
692 693 694
      ..createSync(recursive: true)
      ..writeAsBytesSync(List<int>.filled(10000, 0));

695 696
    await createTestCommandRunner(command).run(
      const <String>['build', 'linux', '--no-pub', '--analyze-size']
697
    );
698

699
    expect(testLogger.statusText, contains('A summary of your Linux bundle analysis can be found at'));
700
    expect(testLogger.statusText, contains('dart devtools --appSizeBase='));
701 702 703
    expect(usage.events, contains(
      const TestUsageEvent('code-size-analysis', 'linux'),
    ));
704 705 706 707 708 709
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
    Platform: () => linuxPlatform,
    FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
    Usage: () => usage,
710
    OperatingSystemUtils: () => FakeOperatingSystemUtils(),
711
  });
712 713

  testUsingContext('Linux on ARM64 build --release passes, and check if the LinuxBuildDirectory for arm64 can be referenced correctly by using analytics', () async {
714
    final BuildCommand command = BuildCommand(
715
      artifacts: artifacts,
716 717
      androidSdk: FakeAndroidSdk(),
      buildSystem: TestBuildSystem.all(BuildResult(success: true)),
718 719 720
      fileSystem: fileSystem,
      logger: logger,
      processUtils: processUtils,
721 722
      osUtils: CustomFakeOperatingSystemUtils(hostPlatform: HostPlatform.linux_arm64),
    );
723
    setUpMockProjectFilesForBuild();
724
    processManager.addCommands(<FakeCommand>[
725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781
      cmakeCommand('release', target: 'arm64'),
      ninjaCommand('release', target: 'arm64', onRun: () {
        fileSystem.file('build/flutter_size_01/snapshot.linux-arm64.json')
          ..createSync(recursive: true)
          ..writeAsStringSync('''
[
  {
    "l": "dart:_internal",
    "c": "SubListIterable",
    "n": "[Optimized] skip",
    "s": 2400
  }
]''');
        fileSystem.file('build/flutter_size_01/trace.linux-arm64.json')
          ..createSync(recursive: true)
          ..writeAsStringSync('{}');
      }),
    ]);

    fileSystem.file('build/linux/arm64/release/bundle/libapp.so')
      ..createSync(recursive: true)
      ..writeAsBytesSync(List<int>.filled(10000, 0));

    await createTestCommandRunner(command).run(
      const <String>['build', 'linux', '--no-pub', '--analyze-size']
    );

    // check if libapp.so of "build/linux/arm64/release" directory can be referenced.
    expect(testLogger.statusText,  contains('libapp.so (Dart AOT)'));
    expect(usage.events, contains(
      const TestUsageEvent('code-size-analysis', 'linux'),
    ));
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
    Platform: () => linuxPlatform,
    FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
    Usage: () => usage,
    OperatingSystemUtils: () => CustomFakeOperatingSystemUtils(hostPlatform: HostPlatform.linux_arm64),
  });
}

class CustomFakeOperatingSystemUtils extends Fake implements OperatingSystemUtils {
  CustomFakeOperatingSystemUtils({
    HostPlatform hostPlatform = HostPlatform.linux_x64
  })  : _hostPlatform = hostPlatform;

  final HostPlatform _hostPlatform;

  @override
  String get name => 'Linux';

  @override
  HostPlatform get hostPlatform => _hostPlatform;

  @override
  List<File> whichAll(String execName) => <File>[];
782
}