1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// 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 'dart:convert';
import 'dart:io';
import 'package:intl/intl.dart';
import 'package:meta/meta.dart';
import 'package:path/path.dart' as path;
import 'package:platform/platform.dart' as platform;
import 'package:process/process.dart';
class CommandException implements Exception {}
Future<void> main() async {
await postProcess();
}
/// Post-processes an APIs documentation zip file to modify the footer and version
/// strings for commits promoted to either beta or stable channels.
Future<void> postProcess() async {
final String revision = await gitRevision(fullLength: true);
print('Docs revision being processed: $revision');
final Directory tmpFolder = Directory.systemTemp.createTempSync();
final String zipDestination = path.join(tmpFolder.path, 'api_docs.zip');
if (!Platform.environment.containsKey('SDK_CHECKOUT_PATH')) {
print('SDK_CHECKOUT_PATH env variable is required for this script');
exit(1);
}
final String checkoutPath = Platform.environment['SDK_CHECKOUT_PATH']!;
final String docsPath = path.join(checkoutPath, 'dev', 'docs');
await runProcessWithValidations(
<String>[
'curl',
'-L',
'https://storage.googleapis.com/flutter_infra_release/flutter/$revision/api_docs.zip',
'--output',
zipDestination,
'--fail',
],
docsPath,
);
// Unzip to docs folder.
await runProcessWithValidations(
<String>[
'unzip',
'-o',
zipDestination,
],
docsPath,
);
// Generate versions file.
await runProcessWithValidations(
<String>['flutter', '--version'],
docsPath,
);
final File versionFile = File('version');
final String version = versionFile.readAsStringSync();
// Recreate footer
final String publishPath = path.join(docsPath, '..', 'docs', 'doc', 'flutter', 'footer.js');
final File footerFile = File(publishPath)..createSync(recursive: true);
createFooter(footerFile, version);
}
/// Gets the git revision of the current checkout. [fullLength] if true will return
/// the full commit hash, if false it will return the first 10 characters only.
Future<String> gitRevision({
bool fullLength = false,
@visibleForTesting platform.Platform platform = const platform.LocalPlatform(),
@visibleForTesting ProcessManager processManager = const LocalProcessManager(),
}) async {
const int kGitRevisionLength = 10;
final ProcessResult gitResult = processManager.runSync(<String>['git', 'rev-parse', 'HEAD']);
if (gitResult.exitCode != 0) {
throw 'git rev-parse exit with non-zero exit code: ${gitResult.exitCode}';
}
final String gitRevision = (gitResult.stdout as String).trim();
if (fullLength) {
return gitRevision;
}
return gitRevision.length > kGitRevisionLength ? gitRevision.substring(0, kGitRevisionLength) : gitRevision;
}
/// Wrapper function to run a subprocess checking exit code and printing stderr and stdout.
/// [executable] is a string with the script/binary to execute, [args] is the list of flags/arguments
/// and [workingDirectory] is as string to the working directory where the subprocess will be run.
Future<void> runProcessWithValidations(
List<String> command,
String workingDirectory, {
@visibleForTesting ProcessManager processManager = const LocalProcessManager(),
bool verbose = true,
}) async {
final ProcessResult result =
processManager.runSync(command, stdoutEncoding: utf8, workingDirectory: workingDirectory);
if (result.exitCode == 0) {
if (verbose) {
print('stdout: ${result.stdout}');
}
} else {
if (verbose) {
print('stderr: ${result.stderr}');
}
throw CommandException();
}
}
/// Get the name of the release branch.
///
/// On LUCI builds, the git HEAD is detached, so first check for the env
/// variable "LUCI_BRANCH"; if it is not set, fall back to calling git.
Future<String> getBranchName({
@visibleForTesting platform.Platform platform = const platform.LocalPlatform(),
@visibleForTesting ProcessManager processManager = const LocalProcessManager(),
}) async {
final RegExp gitBranchRegexp = RegExp(r'^## (.*)');
final String? luciBranch = platform.environment['LUCI_BRANCH'];
if (luciBranch != null && luciBranch.trim().isNotEmpty) {
return luciBranch.trim();
}
final ProcessResult gitResult = processManager.runSync(<String>['git', 'status', '-b', '--porcelain']);
if (gitResult.exitCode != 0) {
throw 'git status exit with non-zero exit code: ${gitResult.exitCode}';
}
final RegExpMatch? gitBranchMatch = gitBranchRegexp.firstMatch((gitResult.stdout as String).trim().split('\n').first);
return gitBranchMatch == null ? '' : gitBranchMatch.group(1)!.split('...').first;
}
/// Updates the footer of the api documentation with the correct branch and versions.
/// [footerPath] is the path to the location of the footer js file and [version] is a
/// string with the version calculated by the flutter tool.
Future<void> createFooter(File footerFile, String version,
{@visibleForTesting String? timestampParam,
@visibleForTesting String? branchParam,
@visibleForTesting String? revisionParam}) async {
final String timestamp = timestampParam ?? DateFormat('yyyy-MM-dd HH:mm').format(DateTime.now());
final String gitBranch = branchParam ?? await getBranchName();
final String revision = revisionParam ?? await gitRevision();
final String gitBranchOut = gitBranch.isEmpty ? '' : '• $gitBranch';
footerFile.writeAsStringSync('''
(function() {
var span = document.querySelector('footer>span');
if (span) {
span.innerText = 'Flutter $version • $timestamp • $revision $gitBranchOut';
}
var sourceLink = document.querySelector('a.source-link');
if (sourceLink) {
sourceLink.href = sourceLink.href.replace('/master/', '/$revision/');
}
})();
''');
}