| // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'dart:async'; |
| import 'dart:io'; |
| |
| import 'package:dartdoc/src/io_utils.dart'; |
| import 'package:path/path.dart' as path; |
| |
| import 'io_utils.dart' as io_utils; |
| import 'subprocess_launcher.dart'; |
| |
| /// A class representing a Flutter SDK repository. |
| class FlutterRepo { |
| final String repoPath; |
| final Map<String, String> env; |
| |
| final String cacheDart; |
| final SubprocessLauncher launcher; |
| |
| FlutterRepo._(this.repoPath, this.env, this.cacheDart, this.launcher); |
| |
| String get dartCmd => path.joinAll([repoPath, 'bin', 'dart']); |
| |
| String get flutterCmd => path.joinAll([repoPath, 'bin', 'flutter']); |
| |
| Future<void> init() async { |
| Directory(repoPath).createSync(recursive: true); |
| await launcher.runStreamed( |
| 'git', ['clone', 'https://github.com/flutter/flutter.git', '.'], |
| workingDirectory: repoPath); |
| await launcher.runStreamed( |
| flutterCmd, |
| ['--version'], |
| workingDirectory: repoPath, |
| ); |
| await launcher.runStreamed( |
| flutterCmd, |
| ['update-packages'], |
| workingDirectory: repoPath, |
| ); |
| } |
| |
| factory FlutterRepo.fromPath(String flutterPath, Map<String, String> env, |
| [String? label]) { |
| var cacheDart = |
| path.join(flutterPath, 'bin', 'cache', 'dart-sdk', 'bin', 'dart'); |
| var flutterBinPath = path.join(path.canonicalize(flutterPath), 'bin'); |
| var existingPathVariable = env['PATH'] ?? Platform.environment['PATH']; |
| env['PATH'] = '$flutterBinPath:$existingPathVariable'; |
| env['FLUTTER_ROOT'] = flutterPath; |
| var launcher = |
| SubprocessLauncher('flutter${label == null ? "" : "-$label"}', env); |
| return FlutterRepo._(flutterPath, env, cacheDart, launcher); |
| } |
| |
| /// Copies an existing, initialized flutter repo to [flutterPath]. |
| static Future<FlutterRepo> copyFromExistingFlutterRepo( |
| FlutterRepo originalRepo, String flutterPath, Map<String, String> env, |
| [String? label]) async { |
| io_utils.copy(Directory(originalRepo.repoPath), Directory(flutterPath)); |
| return FlutterRepo.fromPath(flutterPath, env, label); |
| } |
| |
| /// Doesn't actually copy the existing repo; use for read-only operations |
| /// only. |
| static Future<FlutterRepo> fromExistingFlutterRepo(FlutterRepo originalRepo, |
| [String? label]) async { |
| return FlutterRepo.fromPath(originalRepo.repoPath, {}, label); |
| } |
| } |
| |
| Directory cleanFlutterDir = Directory(path.join( |
| path.context.resolveTildePath('~/.dartdoc_grinder'), 'cleanFlutter')); |
| |
| /// Global so that the lock is retained for the life of the process. |
| Future<void>? _lockFuture; |
| Completer<FlutterRepo>? _cleanFlutterRepo; |
| |
| /// Returns true if we need to replace the existing flutter. We never release |
| /// this lock until the program exits to prevent edge case runs from |
| /// spontaneously deciding to download a new Flutter SDK in the middle of a run. |
| // TODO(srawlins): The above comment is outdated. |
| Future<FlutterRepo> get cleanFlutterRepo async { |
| var repoCompleter = _cleanFlutterRepo; |
| if (repoCompleter != null) { |
| return repoCompleter.future; |
| } |
| |
| // No await is allowed between check of `_cleanFlutterRepo` and its |
| // assignment, to prevent reentering this function. |
| repoCompleter = Completer(); |
| _cleanFlutterRepo = repoCompleter; |
| |
| // Figure out where the repository is supposed to be and lock updates for it. |
| await cleanFlutterDir.parent.create(recursive: true); |
| assert(_lockFuture == null); |
| _lockFuture = File(path.join(cleanFlutterDir.parent.path, 'lock')) |
| .openSync(mode: FileMode.write) |
| .lock(); |
| await _lockFuture; |
| var lastSynced = File(path.join(cleanFlutterDir.parent.path, 'lastSynced')); |
| var newRepo = FlutterRepo.fromPath(cleanFlutterDir.path, {}, 'clean'); |
| |
| // We have a repository, but is it up to date? |
| DateTime? lastSyncedTime; |
| if (lastSynced.existsSync()) { |
| lastSyncedTime = DateTime.fromMillisecondsSinceEpoch( |
| int.parse(lastSynced.readAsStringSync())); |
| } |
| if (lastSyncedTime == null || |
| DateTime.now().difference(lastSyncedTime) > Duration(hours: 24)) { |
| // Rebuild the repository. |
| if (cleanFlutterDir.existsSync()) { |
| cleanFlutterDir.deleteSync(recursive: true); |
| } |
| cleanFlutterDir.createSync(recursive: true); |
| await newRepo.init(); |
| await lastSynced |
| .writeAsString(DateTime.now().millisecondsSinceEpoch.toString()); |
| } |
| repoCompleter.complete(newRepo); |
| _cleanFlutterRepo = repoCompleter; |
| return repoCompleter.future; |
| } |