Matan Lurey b66878a61e
Treat missing --local-engine-host as fatal on CI-like systems. (#132707)
Partial work towards https://github.com/flutter/flutter/issues/132245.

The goal here is to "sniff" out any missing pieces that would block engine builds, rolls, benchmarks and so on before requiring humans to provide the parameter. The implementation is based on a [short discussion with @christopherfujino](https://discord.com/channels/608014603317936148/608022056616853515/1141503921546875110):

@matanlurey:

> Not sure whether to post here or ⁠hackers-infra-🌡 , but is there a way to (and is it advisable to) detect whether the tool is running in a CI environment? I'd like to "soft enforce" --local-engine-host being provided strictly on CI, make sure that lands well, and then "upgrade" it to being non-CI invocations as well (re: https://github.com/flutter/flutter/issues/132245).
>
> Also happy to get talked out of this idea 🙂

@christopherfujino:

> we have a check, lemme find it
> whether or not it is advisable, idk
> https://github.com/flutter/flutter/blob/flutter-3.14-candidate.0/packages/flutter_tools/lib/src/base/bot_detector.dart#L30
>
> (...)
>
> is your desire to get early signal before enforcing t his for humans to prevent functionality churn of landing and reverting and re-landing? 
>
> (yes)
>
> uhh, sure, that's advisable 🙂
2023-08-17 00:41:03 +00:00

457 lines
18 KiB
Dart

// 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 'package:file/memory.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/user_messages.dart';
import 'package:flutter_tools/src/runner/local_engine.dart';
import '../../src/common.dart';
const String kEngineRoot = '/flutter/engine';
const String kArbitraryEngineRoot = '/arbitrary/engine';
const String kDotPackages = '.packages';
void main() {
testWithoutContext('works if --local-engine is specified and --local-engine-src-path '
'is determined by sky_engine', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
fileSystem
.directory('$kArbitraryEngineRoot/src/out/ios_debug/gen/dart-pkg/sky_engine/lib/')
.createSync(recursive: true);
fileSystem
.directory('$kArbitraryEngineRoot/src/out/host_debug')
.createSync(recursive: true);
fileSystem
.file(kDotPackages)
.writeAsStringSync('sky_engine:file://$kArbitraryEngineRoot/src/out/ios_debug/gen/dart-pkg/sky_engine/lib/');
fileSystem
.file('bin/cache/pkg/sky_engine/lib')
.createSync(recursive: true);
final BufferLogger logger = BufferLogger.test();
final LocalEngineLocator localEngineLocator = LocalEngineLocator(
fileSystem: fileSystem,
flutterRoot: '',
logger: logger,
userMessages: UserMessages(),
platform: FakePlatform(environment: <String, String>{}),
);
expect(
await localEngineLocator.findEnginePath(localEngine: 'ios_debug'),
matchesEngineBuildPaths(
hostEngine: '/arbitrary/engine/src/out/host_debug',
targetEngine: '/arbitrary/engine/src/out/ios_debug',
),
);
expect(logger.traceText, contains('Local engine source at /arbitrary/engine/src'));
// Verify that this also works if the sky_engine path is a symlink to the engine root.
fileSystem.link('/symlink').createSync(kArbitraryEngineRoot);
fileSystem
.file(kDotPackages)
.writeAsStringSync('sky_engine:file:///symlink/src/out/ios_debug/gen/dart-pkg/sky_engine/lib/');
expect(
await localEngineLocator.findEnginePath(localEngine: 'ios_debug'),
matchesEngineBuildPaths(
hostEngine: '/symlink/src/out/host_debug',
targetEngine: '/symlink/src/out/ios_debug',
),
);
expect(logger.traceText, contains('Local engine source at /symlink/src'));
});
testWithoutContext('works if --local-engine is specified and --local-engine-src-path '
'is specified', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
// Intentionally do not create a package_config to verify that it is not required.
fileSystem.directory('$kArbitraryEngineRoot/src/out/ios_debug').createSync(recursive: true);
fileSystem.directory('$kArbitraryEngineRoot/src/out/host_debug').createSync(recursive: true);
final BufferLogger logger = BufferLogger.test();
final LocalEngineLocator localEngineLocator = LocalEngineLocator(
fileSystem: fileSystem,
flutterRoot: '',
logger: logger,
userMessages: UserMessages(),
platform: FakePlatform(environment: <String, String>{}),
);
expect(
await localEngineLocator.findEnginePath(engineSourcePath: '$kArbitraryEngineRoot/src', localEngine: 'ios_debug'),
matchesEngineBuildPaths(
hostEngine: '/arbitrary/engine/src/out/host_debug',
targetEngine: '/arbitrary/engine/src/out/ios_debug',
),
);
expect(logger.traceText, contains('Local engine source at /arbitrary/engine/src'));
});
testWithoutContext('works if --local-engine is specified and --local-engine-host is specified', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final Directory localEngine = fileSystem
.directory('$kArbitraryEngineRoot/src/out/android_debug_unopt_arm64/')
..createSync(recursive: true);
fileSystem.directory('$kArbitraryEngineRoot/src/out/host_debug_unopt_arm64/').createSync(recursive: true);
final BufferLogger logger = BufferLogger.test();
final LocalEngineLocator localEngineLocator = LocalEngineLocator(
fileSystem: fileSystem,
flutterRoot: 'flutter/flutter',
logger: logger,
userMessages: UserMessages(),
platform: FakePlatform(environment: <String, String>{}),
);
expect(
await localEngineLocator.findEnginePath(localEngine: localEngine.path, localHostEngine: 'host_debug_unopt_arm64'),
matchesEngineBuildPaths(
hostEngine: '/arbitrary/engine/src/out/host_debug_unopt_arm64',
targetEngine: '/arbitrary/engine/src/out/android_debug_unopt_arm64',
),
);
expect(logger.traceText, contains('Local engine source at /arbitrary/engine/src'));
});
testWithoutContext('works but produces a warning if --local-engine is specified but not --local-host-engine', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final Directory localEngine = fileSystem
.directory('$kArbitraryEngineRoot/src/out/android_debug_unopt_arm64/')
..createSync(recursive: true);
fileSystem.directory('$kArbitraryEngineRoot/src/out/host_debug_unopt/').createSync(recursive: true);
final BufferLogger logger = BufferLogger.test();
final LocalEngineLocator localEngineLocator = LocalEngineLocator(
fileSystem: fileSystem,
flutterRoot: 'flutter/flutter',
logger: logger,
userMessages: UserMessages(),
platform: FakePlatform(environment: <String, String>{}),
);
expect(
await localEngineLocator.findEnginePath(localEngine: localEngine.path),
matchesEngineBuildPaths(
hostEngine: '/arbitrary/engine/src/out/host_debug_unopt',
targetEngine: '/arbitrary/engine/src/out/android_debug_unopt_arm64',
),
);
expect(logger.statusText, contains('Warning! You are using a locally built engine (--local-engine) but have not specified --local-host-engine'));
});
testWithoutContext('fails if --local-engine-host is emitted and treatMissingLocalEngineHostAsFatal is set', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final Directory localEngine = fileSystem
.directory('$kArbitraryEngineRoot/src/out/android_debug_unopt_arm64/')
..createSync(recursive: true);
fileSystem.directory('$kArbitraryEngineRoot/src/out/host_debug_unopt/').createSync(recursive: true);
final BufferLogger logger = BufferLogger.test();
final LocalEngineLocator localEngineLocator = LocalEngineLocator(
fileSystem: fileSystem,
flutterRoot: 'flutter/flutter',
logger: logger,
userMessages: UserMessages(),
platform: FakePlatform(environment: <String, String>{}),
treatMissingLocalEngineHostAsFatal: true,
);
await expectLater(
localEngineLocator.findEnginePath(localEngine: localEngine.path),
throwsToolExit(message: 'You are using a locally built engine (--local-engine) but have not specified --local-host-engine'),
);
});
testWithoutContext('works if --local-engine is specified and --local-engine-src-path '
'is determined by --local-engine', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final Directory localEngine = fileSystem
.directory('$kArbitraryEngineRoot/src/out/ios_debug/')
..createSync(recursive: true);
fileSystem.directory('$kArbitraryEngineRoot/src/out/host_debug/').createSync(recursive: true);
final BufferLogger logger = BufferLogger.test();
final LocalEngineLocator localEngineLocator = LocalEngineLocator(
fileSystem: fileSystem,
flutterRoot: 'flutter/flutter',
logger: logger,
userMessages: UserMessages(),
platform: FakePlatform(environment: <String, String>{}),
);
expect(
await localEngineLocator.findEnginePath(localEngine: localEngine.path),
matchesEngineBuildPaths(
hostEngine: '/arbitrary/engine/src/out/host_debug',
targetEngine: '/arbitrary/engine/src/out/ios_debug',
),
);
expect(logger.traceText, contains('Parsed engine source from local engine as /arbitrary/engine/src'));
expect(logger.traceText, contains('Local engine source at /arbitrary/engine/src'));
});
testWithoutContext('works if local engine is host engine', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final Directory localEngine = fileSystem
.directory('$kArbitraryEngineRoot/src/out/host_debug/')
..createSync(recursive: true);
final BufferLogger logger = BufferLogger.test();
final LocalEngineLocator localEngineLocator = LocalEngineLocator(
fileSystem: fileSystem,
flutterRoot: 'flutter/flutter',
logger: logger,
userMessages: UserMessages(),
platform: FakePlatform(environment: <String, String>{}),
);
expect(
await localEngineLocator.findEnginePath(localEngine: localEngine.path),
matchesEngineBuildPaths(
hostEngine: '/arbitrary/engine/src/out/host_debug',
targetEngine: '/arbitrary/engine/src/out/host_debug',
),
);
expect(logger.traceText, contains('Local engine source at /arbitrary/engine/src'));
});
testWithoutContext('works if local engine is host engine with suffixes', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final Directory localEngine = fileSystem
.directory('$kArbitraryEngineRoot/src/out/host_debug_unopt_arm64/')
..createSync(recursive: true);
final BufferLogger logger = BufferLogger.test();
final LocalEngineLocator localEngineLocator = LocalEngineLocator(
fileSystem: fileSystem,
flutterRoot: 'flutter/flutter',
logger: logger,
userMessages: UserMessages(),
platform: FakePlatform(environment: <String, String>{}),
);
expect(
await localEngineLocator.findEnginePath(localEngine: localEngine.path),
matchesEngineBuildPaths(
hostEngine: '/arbitrary/engine/src/out/host_debug_unopt_arm64',
targetEngine: '/arbitrary/engine/src/out/host_debug_unopt_arm64',
),
);
});
testWithoutContext('works if local engine is simulator', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final Directory localEngine = fileSystem
.directory('$kArbitraryEngineRoot/src/out/ios_debug_sim/')
..createSync(recursive: true);
fileSystem
.directory('$kArbitraryEngineRoot/src/out/host_debug/')
.createSync(recursive: true);
final BufferLogger logger = BufferLogger.test();
final LocalEngineLocator localEngineLocator = LocalEngineLocator(
fileSystem: fileSystem,
flutterRoot: 'flutter/flutter',
logger: logger,
userMessages: UserMessages(),
platform: FakePlatform(environment: <String, String>{}),
);
expect(
await localEngineLocator.findEnginePath(localEngine: localEngine.path),
matchesEngineBuildPaths(
hostEngine: '/arbitrary/engine/src/out/host_debug',
targetEngine: '/arbitrary/engine/src/out/ios_debug_sim',
),
);
});
testWithoutContext('works if local engine is simulator unoptimized',
() async {
final FileSystem fileSystem = MemoryFileSystem.test();
final Directory localEngine = fileSystem
.directory('$kArbitraryEngineRoot/src/out/ios_debug_sim_unopt/')
..createSync(recursive: true);
fileSystem
.directory('$kArbitraryEngineRoot/src/out/host_debug_unopt/')
.createSync(recursive: true);
final BufferLogger logger = BufferLogger.test();
final LocalEngineLocator localEngineLocator = LocalEngineLocator(
fileSystem: fileSystem,
flutterRoot: 'flutter/flutter',
logger: logger,
userMessages: UserMessages(),
platform: FakePlatform(environment: <String, String>{}),
);
expect(
await localEngineLocator.findEnginePath(localEngine: localEngine.path),
matchesEngineBuildPaths(
hostEngine: '/arbitrary/engine/src/out/host_debug_unopt',
targetEngine: '/arbitrary/engine/src/out/ios_debug_sim_unopt',
),
);
});
testWithoutContext('fails if host_debug does not exist', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final Directory localEngine = fileSystem
.directory('$kArbitraryEngineRoot/src/out/ios_debug/')
..createSync(recursive: true);
final LocalEngineLocator localEngineLocator = LocalEngineLocator(
fileSystem: fileSystem,
flutterRoot: 'flutter/flutter',
logger: BufferLogger.test(),
userMessages: UserMessages(),
platform: FakePlatform(environment: <String, String>{}),
);
await expectToolExitLater(
localEngineLocator.findEnginePath(localEngine: localEngine.path),
contains('No Flutter engine build found at /arbitrary/engine/src/out/host_debug'),
);
});
testWithoutContext('works if --local-engine is specified and --local-engine-src-path '
'is determined by flutter root', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
fileSystem.file(kDotPackages).writeAsStringSync('\n');
fileSystem
.directory('$kEngineRoot/src/out/ios_debug')
.createSync(recursive: true);
fileSystem
.directory('$kEngineRoot/src/out/host_debug')
.createSync(recursive: true);
fileSystem
.file('bin/cache/pkg/sky_engine/lib')
.createSync(recursive: true);
final BufferLogger logger = BufferLogger.test();
final LocalEngineLocator localEngineLocator = LocalEngineLocator(
fileSystem: fileSystem,
flutterRoot: 'flutter/flutter',
logger: logger,
userMessages: UserMessages(),
platform: FakePlatform(environment: <String, String>{}),
);
expect(
await localEngineLocator.findEnginePath(localEngine: 'ios_debug'),
matchesEngineBuildPaths(
hostEngine: 'flutter/engine/src/out/host_debug',
targetEngine: 'flutter/engine/src/out/ios_debug',
),
);
expect(logger.traceText, contains('Local engine source at flutter/engine/src'));
});
testWithoutContext('fails if --local-engine is specified and --local-engine-src-path '
'cannot be determined', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final LocalEngineLocator localEngineLocator = LocalEngineLocator(
fileSystem: fileSystem,
flutterRoot: 'flutter/flutter',
logger: BufferLogger.test(),
userMessages: UserMessages(),
platform: FakePlatform(environment: <String, String>{}),
);
await expectToolExitLater(
localEngineLocator.findEnginePath(localEngine: '/path/to/nothing'),
contains('Unable to detect local Flutter engine src directory'),
);
});
testWithoutContext('works for local web engine', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final Directory localWasmEngine = fileSystem
.directory('$kArbitraryEngineRoot/src/out/wasm_whatever/')
..createSync(recursive: true);
final Directory localWebEngine = fileSystem
.directory('$kArbitraryEngineRoot/src/out/web_whatever/')
..createSync(recursive: true);
final BufferLogger wasmLogger = BufferLogger.test();
final LocalEngineLocator localWasmEngineLocator = LocalEngineLocator(
fileSystem: fileSystem,
flutterRoot: 'flutter/flutter',
logger: wasmLogger,
userMessages: UserMessages(),
platform: FakePlatform(environment: <String, String>{}),
);
expect(
await localWasmEngineLocator.findEnginePath(localEngine: localWasmEngine.path),
matchesEngineBuildPaths(
hostEngine: '/arbitrary/engine/src/out/wasm_whatever',
targetEngine: '/arbitrary/engine/src/out/wasm_whatever',
),
);
expect(wasmLogger.traceText, contains('Local engine source at /arbitrary/engine/src'));
final BufferLogger webLogger = BufferLogger.test();
final LocalEngineLocator localWebEngineLocator = LocalEngineLocator(
fileSystem: fileSystem,
flutterRoot: 'flutter/flutter',
logger: webLogger,
userMessages: UserMessages(),
platform: FakePlatform(environment: <String, String>{}),
);
expect(
await localWebEngineLocator.findEnginePath(localEngine: localWebEngine.path),
matchesEngineBuildPaths(
hostEngine: '/arbitrary/engine/src/out/web_whatever',
targetEngine: '/arbitrary/engine/src/out/web_whatever',
),
);
expect(webLogger.traceText, contains('Local engine source at /arbitrary/engine/src'));
});
test('returns null without throwing if nothing is specified', () async {
final LocalEngineLocator localWebEngineLocator = LocalEngineLocator(
fileSystem: MemoryFileSystem.test(),
flutterRoot: 'flutter/flutter',
logger: BufferLogger.test(),
userMessages: UserMessages(),
platform: FakePlatform(environment: <String, String>{}),
);
final EngineBuildPaths? paths = await localWebEngineLocator.findEnginePath();
expect(paths, isNull);
});
test('throws if nothing is specified but the FLUTTER_ENGINE environment variable is set', () async {
final LocalEngineLocator localWebEngineLocator = LocalEngineLocator(
fileSystem: MemoryFileSystem.test(),
flutterRoot: 'flutter/flutter',
logger: BufferLogger.test(),
userMessages: UserMessages(),
platform: FakePlatform(environment: <String, String>{'FLUTTER_ENGINE': 'blah'}),
);
await expectToolExitLater(
localWebEngineLocator.findEnginePath(),
contains('Unable to detect a Flutter engine build directory in blah'),
);
});
}
Matcher matchesEngineBuildPaths({
String? hostEngine,
String? targetEngine,
}) {
return const TypeMatcher<EngineBuildPaths>()
.having((EngineBuildPaths paths) => paths.hostEngine, 'hostEngine', hostEngine)
.having((EngineBuildPaths paths) => paths.targetEngine, 'targetEngine', targetEngine);
}