Add gradle wrapper to project template (#10928)
diff --git a/bin/internal/gradle_wrapper.version b/bin/internal/gradle_wrapper.version new file mode 100644 index 0000000..97e3b0d --- /dev/null +++ b/bin/internal/gradle_wrapper.version
@@ -0,0 +1 @@ +0b5c1398d1d04ac245a310de98825cb7b3278e2a
diff --git a/packages/flutter_tools/lib/src/base/file_system.dart b/packages/flutter_tools/lib/src/base/file_system.dart index 6d5f3bc..6baeecf 100644 --- a/packages/flutter_tools/lib/src/base/file_system.dart +++ b/packages/flutter_tools/lib/src/base/file_system.dart
@@ -71,10 +71,11 @@ } } -/// 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 @@ 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));
diff --git a/packages/flutter_tools/lib/src/base/os.dart b/packages/flutter_tools/lib/src/base/os.dart index 1cb5696..8dfcbb4 100644 --- a/packages/flutter_tools/lib/src/base/os.dart +++ b/packages/flutter_tools/lib/src/base/os.dart
@@ -47,6 +47,8 @@ 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 @@ 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 @@ @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('/'))
diff --git a/packages/flutter_tools/lib/src/cache.dart b/packages/flutter_tools/lib/src/cache.dart index 5d10d2a..007e7a5 100644 --- a/packages/flutter_tools/lib/src/cache.dart +++ b/packages/flutter_tools/lib/src/cache.dart
@@ -17,9 +17,19 @@ /// 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 @@ return fs.file(fs.path.join(getRoot().path, '$artifactName.stamp')); } - bool isUpToDate() { - final MaterialFonts materialFonts = new MaterialFonts(cache); - final FlutterEngine engine = new FlutterEngine(cache); + bool isUpToDate() => _artifacts.every((CachedArtifact artifact) => artifact.isUpToDate()); - return materialFonts.isUpToDate() && engine.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 @@ 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 @@ 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); +/// An artifact managed by the cache. +abstract class CachedArtifact { + CachedArtifact(this.name, this.cache); - static const String kName = 'material_fonts'; - + 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; - return cache.getVersionFor(kName) == cache.getStampFor(kName); + if (version != cache.getStampFor(name)) + return false; + return isUpToDateInner(); } - Future<Null> download() { + 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; + + /// Template method to perform artifact update. + Future<Null> updateInner(); +} + +/// 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); - - final Directory fontsDir = cache.getArtifactDirectory(kName); - if (fontsDir.existsSync()) - fontsDir.deleteSync(recursive: true); - - return Cache._downloadFileToCache( - Uri.parse(cache.getVersionFor(kName)), fontsDir, true - ).then<Null>((Null value) { - cache.setStampFor(kName, cache.getVersionFor(kName)); + return _downloadZipArchive(Uri.parse(version), location).then<Null>((_) { status.stop(); }).whenComplete(status.cancel); } } -class FlutterEngine { +/// A cached artifact containing the Flutter engine binaries. +class FlutterEngine extends CachedArtifact { + FlutterEngine(Cache cache): super('engine', cache); - FlutterEngine(this.cache); - - static const String kName = 'engine'; - static const String kSkyEngine = 'sky_engine'; - - final Cache 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 @@ <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 @@ 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 @@ 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 @@ os.unzip(frameworkZip, framework); } } - - cache.setStampFor(kName, cache.getVersionFor(kName)); } void _makeFilesExecutable(Directory dir) { @@ -386,8 +370,68 @@ 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); +}
diff --git a/packages/flutter_tools/lib/src/commands/create.dart b/packages/flutter_tools/lib/src/commands/create.dart index cb60d49..0b6f67c 100644 --- a/packages/flutter_tools/lib/src/commands/create.dart +++ b/packages/flutter_tools/lib/src/commands/create.dart
@@ -11,6 +11,7 @@ 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 @@ } 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 @@ 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) {
diff --git a/packages/flutter_tools/lib/src/services.dart b/packages/flutter_tools/lib/src/services.dart index 00bd107..1aaf0d2 100644 --- a/packages/flutter_tools/lib/src/services.dart +++ b/packages/flutter_tools/lib/src/services.dart
@@ -69,20 +69,18 @@ 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);
diff --git a/packages/flutter_tools/templates/create/android-java.tmpl/build.gradle b/packages/flutter_tools/templates/create/android-java.tmpl/build.gradle index f5004b9..879b8ca 100644 --- a/packages/flutter_tools/templates/create/android-java.tmpl/build.gradle +++ b/packages/flutter_tools/templates/create/android-java.tmpl/build.gradle
@@ -4,7 +4,7 @@ } dependencies { - classpath 'com.android.tools.build:gradle:2.2.3' + classpath 'com.android.tools.build:gradle:2.3.3' } } @@ -26,7 +26,3 @@ task clean(type: Delete) { delete rootProject.buildDir } - -task wrapper(type: Wrapper) { - gradleVersion = '2.14.1' -}
diff --git a/packages/flutter_tools/templates/create/android-kotlin.tmpl/build.gradle b/packages/flutter_tools/templates/create/android-kotlin.tmpl/build.gradle index b22b7b7..27a170c 100644 --- a/packages/flutter_tools/templates/create/android-kotlin.tmpl/build.gradle +++ b/packages/flutter_tools/templates/create/android-kotlin.tmpl/build.gradle
@@ -4,7 +4,7 @@ } 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 @@ task clean(type: Delete) { delete rootProject.buildDir } - -task wrapper(type: Wrapper) { - gradleVersion = '2.14.1' -}
diff --git a/packages/flutter_tools/templates/create/android.tmpl/.gitignore b/packages/flutter_tools/templates/create/android.tmpl/.gitignore index 1fd9325..1658458 100644 --- a/packages/flutter_tools/templates/create/android.tmpl/.gitignore +++ b/packages/flutter_tools/templates/create/android.tmpl/.gitignore
@@ -7,7 +7,3 @@ /build /captures GeneratedPluginRegistrant.java - -/gradle -/gradlew -/gradlew.bat
diff --git a/packages/flutter_tools/templates/create/android.tmpl/gradle/wrapper/gradle-wrapper.properties b/packages/flutter_tools/templates/create/android.tmpl/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..45e7f14 --- /dev/null +++ b/packages/flutter_tools/templates/create/android.tmpl/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@ +#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
diff --git a/packages/flutter_tools/templates/plugin/android-java.tmpl/build.gradle.tmpl b/packages/flutter_tools/templates/plugin/android-java.tmpl/build.gradle.tmpl index a67e4e9..5ad2e82 100644 --- a/packages/flutter_tools/templates/plugin/android-java.tmpl/build.gradle.tmpl +++ b/packages/flutter_tools/templates/plugin/android-java.tmpl/build.gradle.tmpl
@@ -7,7 +7,7 @@ } dependencies { - classpath 'com.android.tools.build:gradle:2.3.0' + classpath 'com.android.tools.build:gradle:2.3.3' } }
diff --git a/packages/flutter_tools/templates/plugin/android-kotlin.tmpl/build.gradle.tmpl b/packages/flutter_tools/templates/plugin/android-kotlin.tmpl/build.gradle.tmpl index 9d3eecd..02514fd 100644 --- a/packages/flutter_tools/templates/plugin/android-kotlin.tmpl/build.gradle.tmpl +++ b/packages/flutter_tools/templates/plugin/android-kotlin.tmpl/build.gradle.tmpl
@@ -7,7 +7,7 @@ } dependencies { - classpath 'com.android.tools.build:gradle:2.3.0' + classpath 'com.android.tools.build:gradle:2.3.3' } }
diff --git a/packages/flutter_tools/templates/plugin/android.tmpl/.gitignore b/packages/flutter_tools/templates/plugin/android.tmpl/.gitignore index 5c4ef82..c6cbe56 100644 --- a/packages/flutter_tools/templates/plugin/android.tmpl/.gitignore +++ b/packages/flutter_tools/templates/plugin/android.tmpl/.gitignore
@@ -6,7 +6,3 @@ .DS_Store /build /captures - -/gradle -/gradlew -/gradlew.bat
diff --git a/packages/flutter_tools/templates/plugin/android.tmpl/gradle/wrapper/gradle-wrapper.properties b/packages/flutter_tools/templates/plugin/android.tmpl/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..45e7f14 --- /dev/null +++ b/packages/flutter_tools/templates/plugin/android.tmpl/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@ +#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
diff --git a/packages/flutter_tools/test/cache_test.dart b/packages/flutter_tools/test/cache_test.dart index f4b4509..b921260 100644 --- a/packages/flutter_tools/test/cache_test.dart +++ b/packages/flutter_tools/test/cache_test.dart
@@ -48,6 +48,34 @@ 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 MockRandomAccessFile extends Mock implements RandomAccessFile {} +class MockCachedArtifact extends Mock implements CachedArtifact {}