Commit c5999c74 authored by Mikkel Nygaard Ravn's avatar Mikkel Nygaard Ravn Committed by GitHub

Add gradle wrapper to project template (#10928)

parent d97b13b5
0b5c1398d1d04ac245a310de98825cb7b3278e2a
......@@ -71,10 +71,11 @@ void ensureDirectoryExists(String filePath) {
}
}
/// Recursively copies `srcDir` to `destDir`.
/// Recursively copies `srcDir` to `destDir`, invoking [onFileCopied] if
/// specified for each source/destination file pair.
///
/// Creates `destDir` if needed.
void copyDirectorySync(Directory srcDir, Directory destDir) {
void copyDirectorySync(Directory srcDir, Directory destDir, [void onFileCopied(File srcFile, File destFile)]) {
if (!srcDir.existsSync())
throw new Exception('Source directory "${srcDir.path}" does not exist, nothing to copy');
......@@ -86,6 +87,7 @@ void copyDirectorySync(Directory srcDir, Directory destDir) {
if (entity is File) {
final File newFile = destDir.fileSystem.file(newPath);
newFile.writeAsBytesSync(entity.readAsBytesSync());
onFileCopied?.call(entity, newFile);
} else if (entity is Directory) {
copyDirectorySync(
entity, destDir.fileSystem.directory(newPath));
......
......@@ -47,6 +47,8 @@ abstract class OperatingSystemUtils {
void unzip(File file, Directory targetDirectory);
void unpack(File gzippedTarFile, Directory targetDirectory);
/// Returns a pretty name string for the current operating system.
///
/// If available, the detailed version of the OS is included.
......@@ -97,6 +99,12 @@ class _PosixUtils extends OperatingSystemUtils {
runSync(<String>['unzip', '-o', '-q', file.path, '-d', targetDirectory.path]);
}
// tar -xzf tarball -C dest
@override
void unpack(File gzippedTarFile, Directory targetDirectory) {
runSync(<String>['tar', '-xzf', gzippedTarFile.path, '-C', targetDirectory.path]);
}
@override
File makePipe(String path) {
runSync(<String>['mkfifo', path]);
......@@ -167,7 +175,18 @@ class _WindowsUtils extends OperatingSystemUtils {
@override
void unzip(File file, Directory targetDirectory) {
final Archive archive = new ZipDecoder().decodeBytes(file.readAsBytesSync());
_unpackArchive(archive, targetDirectory);
}
@override
void unpack(File gzippedTarFile, Directory targetDirectory) {
final Archive archive = new TarDecoder().decodeBytes(
new GZipDecoder().decodeBytes(gzippedTarFile.readAsBytesSync()),
);
_unpackArchive(archive, targetDirectory);
}
void _unpackArchive(Archive archive, Directory targetDirectory) {
for (ArchiveFile archiveFile in archive.files) {
// The archive package doesn't correctly set isFile.
if (!archiveFile.isFile || archiveFile.name.endsWith('/'))
......
......@@ -17,9 +17,19 @@ import 'globals.dart';
/// A wrapper around the `bin/cache/` directory.
class Cache {
/// [rootOverride] is configurable for testing.
Cache({ Directory rootOverride }) : _rootOverride = rootOverride;
/// [artifacts] is configurable for testing.
Cache({ Directory rootOverride, List<CachedArtifact> artifacts }) : _rootOverride = rootOverride {
if (artifacts == null) {
_artifacts.add(new MaterialFonts(this));
_artifacts.add(new FlutterEngine(this));
_artifacts.add(new GradleWrapper(this));
} else {
_artifacts.addAll(artifacts);
}
}
final Directory _rootOverride;
final List<CachedArtifact> _artifacts = <CachedArtifact>[];
// Initialized by FlutterCommandRunner on startup.
static String flutterRoot;
......@@ -155,16 +165,9 @@ class Cache {
return fs.file(fs.path.join(getRoot().path, '$artifactName.stamp'));
}
bool isUpToDate() {
final MaterialFonts materialFonts = new MaterialFonts(cache);
final FlutterEngine engine = new FlutterEngine(cache);
return materialFonts.isUpToDate() && engine.isUpToDate();
}
bool isUpToDate() => _artifacts.every((CachedArtifact artifact) => artifact.isUpToDate());
Future<String> getThirdPartyFile(String urlStr, String serviceName, {
bool unzip: false
}) async {
Future<String> getThirdPartyFile(String urlStr, String serviceName) async {
final Uri url = Uri.parse(urlStr);
final Directory thirdPartyDir = getArtifactDirectory('third_party');
......@@ -175,7 +178,7 @@ class Cache {
final File cachedFile = fs.file(fs.path.join(serviceDir.path, url.pathSegments.last));
if (!cachedFile.existsSync()) {
try {
await _downloadFileToCache(url, cachedFile, unzip);
await _downloadFile(url, cachedFile);
} catch (e) {
printError('Failed to fetch third-party artifact $url: $e');
rethrow;
......@@ -188,77 +191,65 @@ class Cache {
Future<Null> updateAll() async {
if (!_lockEnabled)
return null;
final MaterialFonts materialFonts = new MaterialFonts(cache);
if (!materialFonts.isUpToDate())
await materialFonts.download();
final FlutterEngine engine = new FlutterEngine(cache);
if (!engine.isUpToDate())
await engine.download();
}
/// Download a file from the given url and write it to the cache.
/// If [unzip] is true, treat the url as a zip file, and unzip it to the
/// directory given.
static Future<Null> _downloadFileToCache(Uri url, FileSystemEntity location, bool unzip) async {
if (!location.parent.existsSync())
location.parent.createSync(recursive: true);
final List<int> fileBytes = await fetchUrl(url);
if (unzip) {
if (location is Directory && !location.existsSync())
location.createSync(recursive: true);
final File tempFile = fs.file(fs.path.join(fs.systemTempDirectory.path, '${url.toString().hashCode}.zip'));
tempFile.writeAsBytesSync(fileBytes, flush: true);
os.unzip(tempFile, location);
tempFile.deleteSync();
} else {
final File file = location;
file.writeAsBytesSync(fileBytes, flush: true);
for (CachedArtifact artifact in _artifacts) {
if (!artifact.isUpToDate())
await artifact.update();
}
}
}
class MaterialFonts {
MaterialFonts(this.cache);
static const String kName = 'material_fonts';
/// An artifact managed by the cache.
abstract class CachedArtifact {
CachedArtifact(this.name, this.cache);
final String name;
final Cache cache;
Directory get location => cache.getArtifactDirectory(name);
String get version => cache.getVersionFor(name);
bool isUpToDate() {
if (!cache.getArtifactDirectory(kName).existsSync())
if (!location.existsSync())
return false;
if (version != cache.getStampFor(name))
return false;
return cache.getVersionFor(kName) == cache.getStampFor(kName);
return isUpToDateInner();
}
Future<Null> download() {
final Status status = logger.startProgress('Downloading Material fonts...', expectSlowOperation: true);
Future<Null> update() async {
if (location.existsSync())
location.deleteSync(recursive: true);
location.createSync(recursive: true);
return updateInner().then<Null>((_) {
cache.setStampFor(name, version);
});
}
/// Hook method for extra checks for being up-to-date.
bool isUpToDateInner() => true;
final Directory fontsDir = cache.getArtifactDirectory(kName);
if (fontsDir.existsSync())
fontsDir.deleteSync(recursive: true);
/// Template method to perform artifact update.
Future<Null> updateInner();
}
return Cache._downloadFileToCache(
Uri.parse(cache.getVersionFor(kName)), fontsDir, true
).then<Null>((Null value) {
cache.setStampFor(kName, cache.getVersionFor(kName));
/// A cached artifact containing fonts used for Material Design.
class MaterialFonts extends CachedArtifact {
MaterialFonts(Cache cache): super('material_fonts', cache);
@override
Future<Null> updateInner() {
final Status status = logger.startProgress('Downloading Material fonts...', expectSlowOperation: true);
return _downloadZipArchive(Uri.parse(version), location).then<Null>((_) {
status.stop();
}).whenComplete(status.cancel);
}
}
class FlutterEngine {
FlutterEngine(this.cache);
static const String kName = 'engine';
static const String kSkyEngine = 'sky_engine';
final Cache cache;
/// A cached artifact containing the Flutter engine binaries.
class FlutterEngine extends CachedArtifact {
FlutterEngine(Cache cache): super('engine', cache);
List<String> _getPackageDirs() => const <String>[kSkyEngine];
List<String> _getPackageDirs() => const <String>['sky_engine'];
// Return a list of (cache directory path, download URL path) tuples.
List<List<String>> _getBinaryDirs() {
......@@ -320,7 +311,8 @@ class FlutterEngine {
<String>['ios-release', 'ios-release/artifacts.zip'],
];
bool isUpToDate() {
@override
bool isUpToDateInner() {
final Directory pkgDir = cache.getCacheDir('pkg');
for (String pkgName in _getPackageDirs()) {
final String pkgPath = fs.path.join(pkgDir.path, pkgName);
......@@ -328,19 +320,17 @@ class FlutterEngine {
return false;
}
final Directory engineDir = cache.getArtifactDirectory(kName);
for (List<String> toolsDir in _getBinaryDirs()) {
final Directory dir = fs.directory(fs.path.join(engineDir.path, toolsDir[0]));
final Directory dir = fs.directory(fs.path.join(location.path, toolsDir[0]));
if (!dir.existsSync())
return false;
}
return cache.getVersionFor(kName) == cache.getStampFor(kName);
return true;
}
Future<Null> download() async {
final String engineVersion = cache.getVersionFor(kName);
final String url = 'https://storage.googleapis.com/flutter_infra/flutter/$engineVersion/';
@override
Future<Null> updateInner() async {
final String url = 'https://storage.googleapis.com/flutter_infra/flutter/$version/';
final Directory pkgDir = cache.getCacheDir('pkg');
for (String pkgName in _getPackageDirs()) {
......@@ -351,14 +341,10 @@ class FlutterEngine {
await _downloadItem('Downloading package $pkgName...', url + pkgName + '.zip', pkgDir);
}
final Directory engineDir = cache.getArtifactDirectory(kName);
if (engineDir.existsSync())
engineDir.deleteSync(recursive: true);
for (List<String> toolsDir in _getBinaryDirs()) {
final String cacheDir = toolsDir[0];
final String urlPath = toolsDir[1];
final Directory dir = fs.directory(fs.path.join(engineDir.path, cacheDir));
final Directory dir = fs.directory(fs.path.join(location.path, cacheDir));
await _downloadItem('Downloading $cacheDir tools...', url + urlPath, dir);
_makeFilesExecutable(dir);
......@@ -370,8 +356,6 @@ class FlutterEngine {
os.unzip(frameworkZip, framework);
}
}
cache.setStampFor(kName, cache.getVersionFor(kName));
}
void _makeFilesExecutable(Directory dir) {
......@@ -386,8 +370,68 @@ class FlutterEngine {
Future<Null> _downloadItem(String message, String url, Directory dest) {
final Status status = logger.startProgress(message, expectSlowOperation: true);
return Cache._downloadFileToCache(Uri.parse(url), dest, true).then<Null>((Null value) {
return _downloadZipArchive(Uri.parse(url), dest).then<Null>((_) {
status.stop();
}).whenComplete(status.cancel);
}
}
/// A cached artifact containing Gradle Wrapper scripts and binaries.
class GradleWrapper extends CachedArtifact {
GradleWrapper(Cache cache): super('gradle_wrapper', cache);
@override
Future<Null> updateInner() async {
final Status status = logger.startProgress('Downloading Gradle Wrapper...', expectSlowOperation: true);
final String url = 'https://android.googlesource.com'
'/platform/tools/base/+archive/$version/templates/gradle/wrapper.tgz';
await _downloadZippedTarball(Uri.parse(url), location).then<Null>((_) {
// Delete property file, allowing templates to provide it.
fs.file(fs.path.join(location.path, 'gradle', 'wrapper', 'gradle-wrapper.properties')).deleteSync();
status.stop();
}).whenComplete(status.cancel);
}
}
/// Download a file from the given [url] and write it to [location].
Future<Null> _downloadFile(Uri url, File location) async {
_ensureExists(location.parent);
final List<int> fileBytes = await fetchUrl(url);
location.writeAsBytesSync(fileBytes, flush: true);
}
/// Download a zip archive from the given [url] and unzip it to [location].
Future<Null> _downloadZipArchive(Uri url, Directory location) {
return _withTemporaryFile('download.zip', (File tempFile) async {
await _downloadFile(url, tempFile);
_ensureExists(location);
os.unzip(tempFile, location);
});
}
/// Download a gzipped tarball from the given [url] and unpack it to [location].
Future<Null> _downloadZippedTarball(Uri url, Directory location) {
return _withTemporaryFile('download.tgz', (File tempFile) async {
await _downloadFile(url, tempFile);
_ensureExists(location);
os.unpack(tempFile, location);
});
}
/// Create a file with the given name in a new temporary directory, invoke
/// [onTemporaryFile] with the file as argument, then delete the temporary
/// directory.
Future<Null> _withTemporaryFile(String name, Future<Null> onTemporaryFile(File file)) async {
final Directory tempDir = fs.systemTempDirectory.createTempSync();
final File tempFile = fs.file(fs.path.join(tempDir.path, name));
await onTemporaryFile(tempFile).whenComplete(() {
tempDir.delete(recursive: true);
});
}
/// Create the given [directory] and parents, as necessary.
void _ensureExists(Directory directory) {
if (!directory.existsSync())
directory.createSync(recursive: true);
}
......@@ -11,6 +11,7 @@ import '../android/android_sdk.dart' as android_sdk;
import '../android/gradle.dart' as gradle;
import '../base/common.dart';
import '../base/file_system.dart';
import '../base/os.dart';
import '../base/utils.dart';
import '../build_info.dart';
import '../cache.dart';
......@@ -167,6 +168,10 @@ class CreateCommand extends FlutterCommand {
}
generatedCount += _renderTemplate('create', appPath, templateContext);
generatedCount += _injectGradleWrapper(appPath);
if (appPath != dirPath) {
generatedCount += _injectGradleWrapper(dirPath);
}
if (argResults['with-driver-test']) {
final String testPath = fs.path.join(appPath, 'test_driver');
generatedCount += _renderTemplate('driver', testPath, templateContext);
......@@ -272,6 +277,22 @@ To edit platform code in an IDE see https://flutter.io/platform-plugins/#edit-co
final Template template = new Template.fromName(templateName);
return template.render(fs.directory(dirPath), context, overwriteExisting: false);
}
int _injectGradleWrapper(String projectDir) {
int filesCreated = 0;
copyDirectorySync(
cache.getArtifactDirectory('gradle_wrapper'),
fs.directory(fs.path.join(projectDir, 'android')),
(File sourceFile, File destinationFile) {
filesCreated++;
final String modes = sourceFile.statSync().modeString();
if (modes != null && modes.contains('x')) {
os.makeExecutable(destinationFile);
}
},
);
return filesCreated;
}
}
String _createAndroidIdentifier(String organization, String name) {
......
......@@ -69,20 +69,18 @@ Future<Null> parseServiceConfigs(
if (jars != null && serviceConfig['jars'] is Iterable) {
for (String jar in serviceConfig['jars'])
jars.add(fs.file(await getServiceFromUrl(jar, serviceRoot, service, unzip: false)));
jars.add(fs.file(await getServiceFromUrl(jar, serviceRoot, service)));
}
}
}
Future<String> getServiceFromUrl(
String url, String rootDir, String serviceName, { bool unzip: false }
) async {
Future<String> getServiceFromUrl(String url, String rootDir, String serviceName) async {
if (url.startsWith("android-sdk:") && androidSdk != null) {
// It's something shipped in the standard android SDK.
return url.replaceAll('android-sdk:', '${androidSdk.directory}/');
} else if (url.startsWith("http")) {
// It's a regular file to download.
return await cache.getThirdPartyFile(url, serviceName, unzip: unzip);
return await cache.getThirdPartyFile(url, serviceName);
} else {
// Assume url is a path relative to the service's root dir.
return fs.path.join(rootDir, url);
......
......@@ -4,7 +4,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
classpath 'com.android.tools.build:gradle:2.3.3'
}
}
......@@ -26,7 +26,3 @@ subprojects {
task clean(type: Delete) {
delete rootProject.buildDir
}
task wrapper(type: Wrapper) {
gradleVersion = '2.14.1'
}
......@@ -4,7 +4,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
classpath 'com.android.tools.build:gradle:2.3.3'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.2-4'
}
}
......@@ -27,7 +27,3 @@ subprojects {
task clean(type: Delete) {
delete rootProject.buildDir
}
task wrapper(type: Wrapper) {
gradleVersion = '2.14.1'
}
......@@ -7,7 +7,3 @@
/build
/captures
GeneratedPluginRegistrant.java
/gradle
/gradlew
/gradlew.bat
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
......@@ -7,7 +7,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.0'
classpath 'com.android.tools.build:gradle:2.3.3'
}
}
......
......@@ -7,7 +7,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.0'
classpath 'com.android.tools.build:gradle:2.3.3'
}
}
......
......@@ -6,7 +6,3 @@
.DS_Store
/build
/captures
/gradle
/gradlew
/gradlew.bat
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
......@@ -48,6 +48,34 @@ void main() {
Platform: () => new FakePlatform()..environment = <String, String>{'FLUTTER_ALREADY_LOCKED': 'true'},
});
});
group('Cache', () {
test('should not be up to date, if some cached artifact is not', () {
final CachedArtifact artifact1 = new MockCachedArtifact();
final CachedArtifact artifact2 = new MockCachedArtifact();
when(artifact1.isUpToDate()).thenReturn(true);
when(artifact2.isUpToDate()).thenReturn(false);
final Cache cache = new Cache(artifacts: <CachedArtifact>[artifact1, artifact2]);
expect(cache.isUpToDate(), isFalse);
});
test('should be up to date, if all cached artifacts are', () {
final CachedArtifact artifact1 = new MockCachedArtifact();
final CachedArtifact artifact2 = new MockCachedArtifact();
when(artifact1.isUpToDate()).thenReturn(true);
when(artifact2.isUpToDate()).thenReturn(true);
final Cache cache = new Cache(artifacts: <CachedArtifact>[artifact1, artifact2]);
expect(cache.isUpToDate(), isTrue);
});
test('should update cached artifacts which are not up to date', () async {
final CachedArtifact artifact1 = new MockCachedArtifact();
final CachedArtifact artifact2 = new MockCachedArtifact();
when(artifact1.isUpToDate()).thenReturn(true);
when(artifact2.isUpToDate()).thenReturn(false);
final Cache cache = new Cache(artifacts: <CachedArtifact>[artifact1, artifact2]);
await cache.updateAll();
verifyNever(artifact1.update());
verify(artifact2.update());
});
});
}
class MockFileSystem extends MemoryFileSystem {
......@@ -65,3 +93,4 @@ class MockFile extends Mock implements File {
}
class MockRandomAccessFile extends Mock implements RandomAccessFile {}
class MockCachedArtifact extends Mock implements CachedArtifact {}
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