blob: 07587d65306140fe8f5d9eff15cc9b90744c6af5 [file] [log] [blame]
// 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 '../artifacts.dart';
import '../base/common.dart';
import '../base/file_system.dart';
import '../build_info.dart';
import '../cache.dart';
import '../globals.dart' as globals;
import '../runner/flutter_command.dart';
/// The directory in the Flutter cache for each platform's artifacts.
const Map<TargetPlatform, String> flutterArtifactPlatformDirectory = <TargetPlatform, String>{
TargetPlatform.windows_x64: 'windows-x64',
TargetPlatform.linux_x64: 'linux-x64',
};
// TODO(jonahwilliams): this should come from a configuration in each build
// directory.
const Map<TargetPlatform, List<String>> artifactFilesByPlatform = <TargetPlatform, List<String>>{
TargetPlatform.windows_x64: <String>[
'flutter_windows.dll',
'flutter_windows.dll.exp',
'flutter_windows.dll.lib',
'flutter_windows.dll.pdb',
'flutter_export.h',
'flutter_messenger.h',
'flutter_plugin_registrar.h',
'flutter_windows.h',
'icudtl.dat',
'cpp_client_wrapper/',
],
};
/// Copies desktop artifacts to local cache directories.
class UnpackCommand extends FlutterCommand {
UnpackCommand() {
argParser.addOption(
'target-platform',
allowed: <String>['windows-x64', 'linux-x64'],
);
argParser.addOption('cache-dir',
help: 'Location to output platform specific artifacts.');
}
@override
String get description => '(DEPRECATED) unpack desktop artifacts';
@override
String get name => 'unpack';
@override
bool get hidden => true;
@override
Future<Set<DevelopmentArtifact>> get requiredArtifacts async {
final Set<DevelopmentArtifact> result = <DevelopmentArtifact>{};
final TargetPlatform targetPlatform = getTargetPlatformForName(stringArg('target-platform'));
switch (targetPlatform) {
case TargetPlatform.windows_x64:
result.add(DevelopmentArtifact.windows);
break;
case TargetPlatform.linux_x64:
result.add(DevelopmentArtifact.linux);
break;
default:
}
return result;
}
@override
Future<FlutterCommandResult> runCommand() async {
final String targetName = stringArg('target-platform');
final String targetDirectory = stringArg('cache-dir');
if (!globals.fs.directory(targetDirectory).existsSync()) {
globals.fs.directory(targetDirectory).createSync(recursive: true);
}
final TargetPlatform targetPlatform = getTargetPlatformForName(targetName);
final ArtifactUnpacker flutterArtifactFetcher = ArtifactUnpacker(targetPlatform);
bool success = true;
if (globals.artifacts is LocalEngineArtifacts) {
final LocalEngineArtifacts localEngineArtifacts = globals.artifacts as LocalEngineArtifacts;
success = flutterArtifactFetcher.copyLocalBuildArtifacts(
localEngineArtifacts.engineOutPath,
targetDirectory,
);
} else {
success = flutterArtifactFetcher.copyCachedArtifacts(
targetDirectory,
);
}
if (!success) {
throwToolExit('Failed to unpack desktop artifacts.');
}
return FlutterCommandResult.success();
}
}
/// Manages the copying of cached or locally built Flutter artifacts, including
/// tracking the last-copied versions and updating only if necessary.
class ArtifactUnpacker {
/// Creates a new fetcher for the given configuration.
const ArtifactUnpacker(this.platform);
/// The platform to copy artifacts for.
final TargetPlatform platform;
/// Checks [targetDirectory] to see if artifacts have already been copied for
/// the current hash, and if not, copies the artifacts for [platform] from the
/// Flutter cache (after ensuring that the cache is present).
///
/// Returns true if the artifacts were successfully copied, or were already
/// present with the correct hash.
bool copyCachedArtifacts(String targetDirectory) {
String cacheStamp;
switch (platform) {
case TargetPlatform.windows_x64:
cacheStamp = 'windows-sdk';
break;
case TargetPlatform.linux_x64:
return true;
default:
throwToolExit('Unsupported target platform: $platform');
}
final String targetHash =
readHashFileIfPossible(globals.cache.getStampFileFor(cacheStamp));
if (targetHash == null) {
globals.printError('Failed to find engine stamp file');
return false;
}
try {
final String currentHash = _lastCopiedHash(targetDirectory);
if (currentHash == null || targetHash != currentHash) {
// Copy them to the target directory.
final String flutterCacheDirectory = globals.fs.path.join(
Cache.flutterRoot,
'bin',
'cache',
'artifacts',
'engine',
flutterArtifactPlatformDirectory[platform],
);
if (!_copyArtifactFiles(flutterCacheDirectory, targetDirectory)) {
return false;
}
_setLastCopiedHash(targetDirectory, targetHash);
globals.printTrace('Copied artifacts for version $targetHash.');
} else {
globals.printTrace('Artifacts for version $targetHash already present.');
}
} catch (error, stackTrace) {
globals.printError(stackTrace.toString());
globals.printError(error.toString());
return false;
}
return true;
}
/// Acts like [copyCachedArtifacts], replacing the artifacts and updating
/// the version stamp, except that it pulls the artifact from a local engine
/// build with the given [buildConfiguration] (e.g., host_debug_unopt) whose
/// checkout is rooted at [engineRoot].
bool copyLocalBuildArtifacts(String buildOutput, String targetDirectory) {
if (!_copyArtifactFiles(buildOutput, targetDirectory)) {
return false;
}
// Update the hash file to indicate that it's a local build, so that it's
// obvious where it came from.
_setLastCopiedHash(targetDirectory, 'local build: $buildOutput');
return true;
}
/// Copies the artifact files for [platform] from [sourceDirectory] to
/// [targetDirectory].
bool _copyArtifactFiles(String sourceDirectory, String targetDirectory) {
final List<String> artifactFiles = artifactFilesByPlatform[platform];
if (artifactFiles == null) {
globals.printError('Unsupported platform: $platform.');
return false;
}
try {
globals.fs.directory(targetDirectory).createSync(recursive: true);
for (final String entityName in artifactFiles) {
final String sourcePath = globals.fs.path.join(sourceDirectory, entityName);
final String targetPath = globals.fs.path.join(targetDirectory, entityName);
if (entityName.endsWith('/')) {
globals.fsUtils.copyDirectorySync(
globals.fs.directory(sourcePath),
globals.fs.directory(targetPath),
);
} else {
globals.fs.file(sourcePath)
.copySync(globals.fs.path.join(targetDirectory, entityName));
}
}
globals.printTrace('Copied artifacts from $sourceDirectory.');
} catch (e, stackTrace) {
globals.printError(e.message as String);
globals.printError(stackTrace.toString());
return false;
}
return true;
}
/// Returns a File object for the file containing the last copied hash
/// in [directory].
File _lastCopiedHashFile(String directory) {
return globals.fs.file(globals.fs.path.join(directory, '.last_artifact_version'));
}
/// Returns the hash of the artifacts last copied to [directory], or null if
/// they haven't been copied.
String _lastCopiedHash(String directory) {
// Sanity check that at least one file is present; this won't catch every
// case, but handles someone deleting all the non-hidden cached files to
// force fresh copy.
final String artifactFilePath = globals.fs.path.join(
directory,
artifactFilesByPlatform[platform].first,
);
if (!globals.fs.file(artifactFilePath).existsSync()) {
return null;
}
final File hashFile = _lastCopiedHashFile(directory);
return readHashFileIfPossible(hashFile);
}
/// Writes [hash] to the file that stores the last copied hash for
/// in [directory].
void _setLastCopiedHash(String directory, String hash) {
_lastCopiedHashFile(directory).writeAsStringSync(hash);
}
/// Returns the engine hash from [file] as a String, or null.
///
/// If the file is missing, or cannot be read, returns null.
String readHashFileIfPossible(File file) {
if (!file.existsSync()) {
return null;
}
try {
return file.readAsStringSync().trim();
} on FileSystemException {
// If the file can't be read for any reason, just treat it as missing.
return null;
}
}
}