Commit b5f763b4 authored by Chris Bracken's avatar Chris Bracken Committed by GitHub

Support a configurable build directory (#5601)

This change adds a top-level getBuildDirectory func and funcs for
android, aot, asset, ios build products.

Developers may now add a "build-dir" mapping to their
~/.flutter_settings (JSON format) config file. Output directory is
relative to the main flutter application directory.

This change also changes the default build directory for iOS builds to a
subdirectory of the configured build directory, 'build/ios' by default.
parent 852a00a1
......@@ -65,7 +65,7 @@ BuildApp() {
AssertExists ${target_path}
local build_dir=${SYMROOT:-build}
local build_dir=${FLUTTER_BUILD_DIR:-build}
local local_engine_flag=""
if [[ -n "$LOCAL_ENGINE" ]]; then
local_engine_flag="--local-engine=$LOCAL_ENGINE"
......
......@@ -83,7 +83,7 @@ class AndroidApk extends ApplicationPackage {
apkPath = gradleAppOut;
} else {
manifestPath = path.join('android', 'AndroidManifest.xml');
apkPath = path.join('build', 'app.apk');
apkPath = path.join(getAndroidBuildDirectory(), 'app.apk');
}
if (!FileSystemEntity.isFileSync(manifestPath))
......@@ -157,7 +157,7 @@ class IOSApp extends ApplicationPackage {
String get deviceBundlePath => _buildAppPath('iphoneos');
String _buildAppPath(String type) {
return path.join(appDirectory, 'build', 'Release-$type', kBundleName);
return path.join(getIosBuildDirectory(), 'Release-$type', kBundleName);
}
}
......
......@@ -10,6 +10,7 @@ import 'package:json_schema/json_schema.dart';
import 'package:path/path.dart' as path;
import 'package:yaml/yaml.dart';
import 'build_info.dart';
import 'cache.dart';
import 'dart/package_map.dart';
import 'globals.dart';
......@@ -48,7 +49,6 @@ class AssetBundle {
final Set<AssetBundleEntry> entries = new Set<AssetBundleEntry>();
static const String defaultManifestPath = 'flutter.yaml';
static const String defaultWorkingDirPath = 'build/flx';
static const String _kFontSetMaterial = 'material';
static const String _kFontSetRoboto = 'roboto';
......@@ -67,10 +67,11 @@ class AssetBundle {
Future<int> build({
String manifestPath: defaultManifestPath,
String workingDirPath: defaultWorkingDirPath,
String workingDirPath,
bool includeRobotoFonts: true,
bool reportLicensedPackages: false
}) async {
workingDirPath ??= getAssetBuildDirectory();
Object manifest = _loadFlutterYamlManifest(manifestPath);
if (manifest == null) {
// No manifest file found for this application.
......@@ -94,7 +95,7 @@ class AssetBundle {
packageMap,
manifestDescriptor,
assetBasePath,
excludeDirs: <String>[workingDirPath, path.join(assetBasePath, 'build')]
excludeDirs: <String>[workingDirPath, getBuildDirectory()]
);
if (assetVariants == null)
......
......@@ -19,6 +19,8 @@ class Config {
static Config get instance => context[Config] ?? (context[Config] = new Config());
File _configFile;
String get configPath => _configFile.path;
Map<String, dynamic> _values = <String, dynamic>{};
Iterable<String> get keys => _values.keys;
......
......@@ -4,6 +4,8 @@
import 'dart:io';
import 'package:path/path.dart' as path;
import 'base/utils.dart';
import 'globals.dart';
......@@ -113,3 +115,34 @@ HostPlatform getCurrentHostPlatform() {
return HostPlatform.linux_x64;
}
/// Returns the top-level build output directory.
String getBuildDirectory() {
String buildDir = config.getValue('build-dir') ?? 'build';
if (path.isAbsolute(buildDir)) {
throw new Exception(
'build-dir config setting in ${config.configPath} must be relative');
}
return buildDir;
}
/// Returns the Android build output directory.
String getAndroidBuildDirectory() {
// TODO(cbracken) move to android subdir.
return getBuildDirectory();
}
/// Returns the AOT build output directory.
String getAotBuildDirectory() {
return path.join(getBuildDirectory(), 'aot');
}
/// Returns the asset build output directory.
String getAssetBuildDirectory() {
return path.join(getBuildDirectory(), 'flx');
}
/// Returns the iOS build output directory.
String getIosBuildDirectory() {
return path.join(getBuildDirectory(), 'ios');
}
......@@ -7,6 +7,7 @@ import 'dart:io';
import 'package:meta/meta.dart';
import '../build_info.dart';
import '../globals.dart';
import '../runner/flutter_command.dart';
import '../base/utils.dart';
......@@ -66,7 +67,7 @@ class BuildCleanCommand extends FlutterCommand {
@override
Future<int> runInProject() async {
Directory buildDir = new Directory('build');
Directory buildDir = new Directory(getBuildDirectory());
printStatus("Deleting '${buildDir.path}${Platform.pathSeparator}'.");
if (!buildDir.existsSync())
......
......@@ -16,8 +16,6 @@ import '../globals.dart';
import '../resident_runner.dart';
import 'build.dart';
const String _kDefaultAotOutputDir = 'build/aot';
// Files generated by the ahead-of-time snapshot builder.
const List<String> kAotSnapshotFiles = const <String>[
'snapshot_aot_instr', 'snapshot_aot_isolate', 'snapshot_aot_rodata', 'snapshot_aot_vmisolate',
......@@ -29,7 +27,7 @@ class BuildAotCommand extends BuildSubCommand {
addBuildModeFlags();
usesPubOption();
argParser
..addOption('output-dir', defaultsTo: _kDefaultAotOutputDir)
..addOption('output-dir', defaultsTo: getAotBuildDirectory())
..addOption('target-platform',
defaultsTo: 'android-arm',
allowed: <String>['android-arm', 'ios']
......@@ -82,9 +80,10 @@ Future<String> buildAotSnapshot(
String mainPath,
TargetPlatform platform,
BuildMode buildMode, {
String outputPath: _kDefaultAotOutputDir,
String outputPath,
bool interpreter: false
}) async {
outputPath ??= getAotBuildDirectory();
try {
return _buildAotSnapshot(
mainPath,
......@@ -104,9 +103,10 @@ Future<String> _buildAotSnapshot(
String mainPath,
TargetPlatform platform,
BuildMode buildMode, {
String outputPath: _kDefaultAotOutputDir,
String outputPath,
bool interpreter: false
}) async {
outputPath ??= getAotBuildDirectory();
if (!isAotBuildMode(buildMode) && !interpreter) {
printError('${toTitleCase(getModeName(buildMode))} mode does not support AOT compilation.');
return null;
......
......@@ -26,7 +26,6 @@ import 'build.dart';
export '../android/android_device.dart' show AndroidDevice;
const String _kDefaultAndroidManifestPath = 'android/AndroidManifest.xml';
const String _kDefaultOutputPath = 'build/app.apk';
const String _kDefaultResourcesPath = 'android/res';
const String _kDefaultAssetsPath = 'android/assets';
......@@ -39,6 +38,9 @@ const String _kDebugKeystoreKeyAlias = "chromiumdebugkey";
// Password for the Chromium debug keystore
const String _kDebugKeystorePassword = "chromium";
// Default APK output path.
String get _defaultOutputPath => path.join(getAndroidBuildDirectory(), 'app.apk');
/// Copies files into a new directory structure.
class _AssetBuilder {
final Directory outDir;
......@@ -174,7 +176,7 @@ class BuildApkCommand extends BuildSubCommand {
help: 'Resources directory path.');
argParser.addOption('output-file',
abbr: 'o',
defaultsTo: _kDefaultOutputPath,
defaultsTo: _defaultOutputPath,
help: 'Output APK file.');
argParser.addOption('flx',
abbr: 'f',
......@@ -443,12 +445,14 @@ Future<int> buildAndroid(
bool force: false,
String manifest: _kDefaultAndroidManifestPath,
String resources,
String outputFile: _kDefaultOutputPath,
String outputFile,
String target,
String flxPath,
String aotPath,
ApkKeystoreInfo keystore
}) async {
outputFile ??= _defaultOutputPath;
// Validate that we can find an android sdk.
if (androidSdk == null) {
printError('No Android SDK found. Try setting the ANDROID_HOME environment variable.');
......
......@@ -4,6 +4,7 @@
import 'dart:async';
import '../build_info.dart';
import '../flx.dart';
import '../globals.dart';
import 'build.dart';
......@@ -20,7 +21,7 @@ class BuildFlxCommand extends BuildSubCommand {
argParser.addOption('output-file', abbr: 'o', defaultsTo: defaultFlxOutputPath);
argParser.addOption('snapshot', defaultsTo: defaultSnapshotPath);
argParser.addOption('depfile', defaultsTo: defaultDepfilePath);
argParser.addOption('working-dir', defaultsTo: defaultWorkingDirPath);
argParser.addOption('working-dir', defaultsTo: getAssetBuildDirectory());
argParser.addFlag('include-roboto-fonts', defaultsTo: true);
argParser.addFlag('report-licensed-packages', help: 'Whether to report the names of all the packages that are included in the application\'s LICENSE file.', defaultsTo: false);
usesPubOption();
......
......@@ -15,7 +15,7 @@ import '../globals.dart';
import '../resident_runner.dart';
import '../runner/flutter_command.dart';
const String _kDefaultBundlePath = 'build/app.flx';
String get _defaultBundlePath => path.join(getBuildDirectory(), 'app.flx');
class RunMojoCommand extends FlutterCommand {
RunMojoCommand({ this.hidden: false }) {
......@@ -140,7 +140,7 @@ class RunMojoCommand extends FlutterCommand {
String targetApp = argResults['app'];
if (targetApp == null) {
targetApp = _kDefaultBundlePath;
targetApp = _defaultBundlePath;
String mainPath = findMainDartFile(argResults['target']);
......
......@@ -6,8 +6,11 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:path/path.dart' as path;
import '../base/common.dart';
import '../base/utils.dart';
import '../build_info.dart';
import '../cache.dart';
import '../globals.dart';
import '../vmservice.dart';
......@@ -153,7 +156,8 @@ class Tracing {
/// Download the startup trace information from the given observatory client and
/// store it to build/start_up_info.json.
Future<Null> downloadStartupTrace(VMService observatory) async {
File traceInfoFile = new File('build/start_up_info.json');
String traceInfoFilePath = path.join(getBuildDirectory(), 'start_up_info.json');
File traceInfoFile = new File(traceInfoFilePath);
// Delete old startup data, if any.
if (await traceInfoFile.exists())
......
......@@ -9,6 +9,7 @@ import 'dart:io';
import 'package:path/path.dart' as path;
import 'base/logger.dart';
import 'build_info.dart';
import 'dart/package_map.dart';
import 'asset.dart';
import 'globals.dart';
......@@ -335,9 +336,10 @@ class DevFS {
status = logger.startProgress('Scanning asset files...');
// Synchronize asset bundle.
for (AssetBundleEntry entry in bundle.entries) {
// We write the assets into 'build/flx' so that they are in the
// same location in DevFS and the iOS simulator.
final String devicePath = path.join('build/flx', entry.archivePath);
// We write the assets into the AssetBundle working dir so that they
// are in the same location in DevFS and the iOS simulator.
final String devicePath =
path.join(getAssetBuildDirectory(), entry.archivePath);
_scanBundleEntry(devicePath, entry, bundleDirty);
}
status.stop(showElapsedTime: true);
......@@ -457,7 +459,7 @@ class DevFS {
bool _shouldIgnore(String devicePath) {
List<String> ignoredPrefixes = <String>['android/',
'build/',
getBuildDirectory(),
'ios/',
'.pub/'];
for (String ignoredPrefix in ignoredPrefixes) {
......
......@@ -11,6 +11,7 @@ import 'asset.dart';
import 'base/file_system.dart' show ensureDirectoryExists;
import 'base/process.dart';
import 'dart/package_map.dart';
import 'build_info.dart';
import 'globals.dart';
import 'toolchain.dart';
import 'zip.dart';
......@@ -18,11 +19,10 @@ import 'zip.dart';
const String defaultMainPath = 'lib/main.dart';
const String defaultAssetBasePath = '.';
const String defaultManifestPath = 'flutter.yaml';
const String defaultFlxOutputPath = 'build/app.flx';
const String defaultSnapshotPath = 'build/snapshot_blob.bin';
const String defaultDepfilePath = 'build/snapshot_blob.bin.d';
String get defaultFlxOutputPath => path.join(getBuildDirectory(), 'app.flx');
String get defaultSnapshotPath => path.join(getBuildDirectory(), 'snapshot_blob.bin');
String get defaultDepfilePath => path.join(getBuildDirectory(), 'snapshot_blob.bin.d');
const String defaultPrivateKeyPath = 'privatekey.der';
const String defaultWorkingDirPath = 'build/flx';
const String _kSnapshotKey = 'snapshot_blob.bin';
......@@ -47,7 +47,7 @@ Future<int> createSnapshot({
return runCommandAndStreamOutput(args);
}
/// Build the flx in the build/ directory and return `localBundlePath` on success.
/// Build the flx in the build directory and return `localBundlePath` on success.
///
/// Return `null` on failure.
Future<String> buildFlx({
......@@ -56,30 +56,32 @@ Future<String> buildFlx({
bool includeRobotoFonts: true
}) async {
int result;
String localBundlePath = path.join('build', 'app.flx');
String localSnapshotPath = path.join('build', 'snapshot_blob.bin');
result = await build(
snapshotPath: localSnapshotPath,
outputPath: localBundlePath,
snapshotPath: defaultSnapshotPath,
outputPath: defaultFlxOutputPath,
mainPath: mainPath,
precompiledSnapshot: precompiledSnapshot,
includeRobotoFonts: includeRobotoFonts
);
return result == 0 ? localBundlePath : null;
return result == 0 ? defaultFlxOutputPath : null;
}
Future<int> build({
String mainPath: defaultMainPath,
String manifestPath: defaultManifestPath,
String outputPath: defaultFlxOutputPath,
String snapshotPath: defaultSnapshotPath,
String depfilePath: defaultDepfilePath,
String outputPath,
String snapshotPath,
String depfilePath,
String privateKeyPath: defaultPrivateKeyPath,
String workingDirPath: defaultWorkingDirPath,
String workingDirPath,
bool precompiledSnapshot: false,
bool includeRobotoFonts: true,
bool reportLicensedPackages: false
}) async {
outputPath ??= defaultFlxOutputPath;
snapshotPath ??= defaultSnapshotPath;
depfilePath ??= defaultDepfilePath;
workingDirPath ??= getAssetBuildDirectory();
File snapshotFile;
if (!precompiledSnapshot) {
......@@ -114,12 +116,14 @@ Future<int> build({
Future<int> assemble({
String manifestPath,
File snapshotFile,
String outputPath: defaultFlxOutputPath,
String outputPath,
String privateKeyPath: defaultPrivateKeyPath,
String workingDirPath: defaultWorkingDirPath,
String workingDirPath,
bool includeRobotoFonts: true,
bool reportLicensedPackages: false
}) async {
outputPath ??= defaultFlxOutputPath;
workingDirPath ??= getAssetBuildDirectory();
printTrace('Building $outputPath');
// Build the asset bundle.
......
......@@ -13,6 +13,7 @@ import 'asset.dart';
import 'base/logger.dart';
import 'base/process.dart';
import 'base/utils.dart';
import 'build_info.dart';
import 'cache.dart';
import 'commands/build_apk.dart';
import 'commands/install.dart';
......@@ -380,7 +381,7 @@ class HotRunner extends ResidentRunner {
String devicePackagesPath =
_devFS.baseUri.resolve('.packages').toFilePath();
String deviceAssetsDirectoryPath =
_devFS.baseUri.resolve('build/flx').toFilePath();
_devFS.baseUri.resolve(getAssetBuildDirectory()).toFilePath();
await _launchInView(deviceEntryPath,
devicePackagesPath,
deviceAssetsDirectoryPath);
......
......@@ -28,6 +28,11 @@ void updateXcodeGeneratedProperties(String projectPath, BuildMode mode, String t
// The runtime mode for the current build.
localsBuffer.writeln('FLUTTER_BUILD_MODE=${getModeName(mode)}');
// The build outputs directory, relative to FLUTTER_APPLICATION_PATH.
localsBuffer.writeln('FLUTTER_BUILD_DIR=${getBuildDirectory()}');
localsBuffer.writeln('SYMROOT=\${SOURCE_ROOT}/../${getIosBuildDirectory()}');
String flutterFrameworkDir = path.normalize(tools.getEngineArtifactsDirectory(TargetPlatform.ios, mode).path);
localsBuffer.writeln('FLUTTER_FRAMEWORK_DIR=$flutterFrameworkDir');
......
......@@ -476,7 +476,7 @@ class IOSSimulator extends Device {
// Prepare launch arguments.
List<String> args = <String>[
"--flx=${path.absolute(path.join('build', 'app.flx'))}",
"--flx=${path.absolute(path.join(getBuildDirectory(), 'app.flx'))}",
"--dart-main=${path.absolute(mainPath)}",
"--packages=${path.absolute('.packages')}",
];
......
......@@ -5,6 +5,7 @@
import 'dart:io';
import 'package:flutter_tools/src/asset.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/devfs.dart';
import 'package:path/path.dart' as path;
import 'package:test/test.dart';
......@@ -59,17 +60,20 @@ void main() {
});
testUsingContext('add file in an asset bundle', () async {
await devFS.update(bundle: assetBundle, bundleDirty: true);
expect(devFSOperations.contains('writeFile test build/flx/a.txt'), isTrue);
expect(devFSOperations.contains(
'writeFile test ${getAssetBuildDirectory()}/a.txt'), isTrue);
});
testUsingContext('add a file to the asset bundle', () async {
assetBundle.entries.add(new AssetBundleEntry.fromString('b.txt', ''));
await devFS.update(bundle: assetBundle, bundleDirty: true);
expect(devFSOperations.contains('writeFile test build/flx/b.txt'), isTrue);
expect(devFSOperations.contains(
'writeFile test ${getAssetBuildDirectory()}/b.txt'), isTrue);
});
testUsingContext('delete a file from the asset bundle', () async {
assetBundle.entries.clear();
await devFS.update(bundle: assetBundle, bundleDirty: true);
expect(devFSOperations.contains('deleteFile test build/flx/b.txt'), isTrue);
expect(devFSOperations.contains(
'deleteFile test ${getAssetBuildDirectory()}/b.txt'), isTrue);
});
testUsingContext('delete dev file system', () async {
await devFS.destroy();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment