Skip to content

Commit a1da087

Browse files
authored
parse action versions out of workflow files (#420)
- parse action versions out of workflow files when the versions in that action file are managed by dependabot This will allow us to configure dependabot version upgrades for repos that use mono_repo to generate (and re-generate) their actions configurations. Code generate the hard coded versions from our own workflow file so that dependabot can manage that as well.
1 parent 69c4392 commit a1da087

File tree

16 files changed

+355
-58
lines changed

16 files changed

+355
-58
lines changed

.github/workflows/dart.yml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

mono_repo/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 6.5.7
2+
3+
- Updated `mono_repo` to use the existing action versions from the generated
4+
workflow file when dependabot is configured for the repo.
5+
16
## 6.5.6
27

38
- Restore the previous ordering behavior for jobs by using a secondary sort

mono_repo/README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,5 +228,20 @@ Look at these repositories for examples of `mono_repo` usage:
228228
* https://github.com/dart-lang/webdev
229229
* https://github.com/google/json_serializable.dart
230230

231+
## Mono_repo and Dependabot
232+
233+
Historically, package:mono_repo and Dependabot couldn't be used together -
234+
they both wanted to maintain your GitHub workflow file. We've adopted mono_repo
235+
so that you can now use both it and Dependabot in your repo.
236+
237+
When generating your workflow configuration (`mono_repo generate`) mono_repo
238+
will write out its current default action versions into the workflow file. If
239+
however it sees that the repo has a Dependabot configuration - has a file named
240+
`.github/dependabot.yaml` in the repo - mono_repo will instead parse the
241+
workflow file, read out the current action versions, and use those versions when
242+
re-generating the file. This lets mono_repo manage the overall structure of the
243+
file, while allowing Dependabot to independently move various action versions
244+
forward as necessary.
245+
231246
[Dart packages]: https://dart.dev/guides/libraries/create-library-packages
232247
[setup your PATH]: https://dart.dev/tools/pub/cmd/pub-global#running-a-script-from-your-path

mono_repo/lib/src/commands/github/action_info.dart

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,43 @@
1+
import '../../root_config.dart';
2+
import 'action_versions.dart';
13
import 'job.dart';
24
import 'step.dart';
35

46
enum ActionInfo implements Comparable<ActionInfo> {
57
cache(
68
name: 'Cache Pub hosted dependencies',
79
repo: 'actions/cache',
8-
version: '88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8', // v3.3.1
10+
version: actionsCacheVersion,
911
),
1012
checkout(
1113
name: 'Checkout repository',
1214
repo: 'actions/checkout',
13-
version: '8e5e7e5ab8b370d6c329ec480221332ada57f0ab', // v3.5.2
15+
version: actionsCheckoutVersion,
1416
),
1517
setupDart(
1618
name: 'Setup Dart SDK',
1719
repo: 'dart-lang/setup-dart',
18-
version: 'd6a63dab3335f427404425de0fbfed4686d93c4f', // v1.5.0
20+
version: dartLangSetupDartVersion,
1921
),
2022
setupFlutter(
2123
name: 'Setup Flutter SDK',
2224
repo: 'subosito/flutter-action',
23-
version: '48cafc24713cca54bbe03cdc3a423187d413aafa', // v2.10.0
25+
version: subositoFlutterActionVersion,
2426
),
2527

2628
/// See https://github.com/marketplace/actions/coveralls-github-action
2729
coveralls(
2830
name: 'Upload coverage to Coveralls',
2931
repo: 'coverallsapp/github-action',
30-
version: 'master',
32+
version: coverallsappGithubActionVersion,
3133
completionJobFactory: _coverageCompletionJob,
3234
),
3335

3436
/// See https://github.com/marketplace/actions/codecov
3537
codecov(
3638
name: 'Upload coverage to codecov.io',
3739
repo: 'codecov/codecov-action',
38-
version: 'main',
40+
version: codecovCodecovActionVersion,
3941
);
4042

4143
const ActionInfo({
@@ -48,16 +50,19 @@ enum ActionInfo implements Comparable<ActionInfo> {
4850
final String repo;
4951
final String version;
5052
final String name;
51-
final Job Function()? completionJobFactory;
53+
final Job Function(RootConfig rootConfig)? completionJobFactory;
5254

5355
Step usage({
5456
String? name,
5557
String? id,
5658
Map<String, dynamic>? withContent,
59+
Map<String, String>? versionOverrides,
5760
}) {
5861
name ??= this.name;
62+
final useVersion =
63+
(versionOverrides == null ? null : versionOverrides[repo]) ?? version;
5964
final step = Step.uses(
60-
uses: '$repo@$version',
65+
uses: '$repo@$useVersion',
6166
id: id,
6267
name: name,
6368
withContent: withContent,
@@ -71,7 +76,7 @@ enum ActionInfo implements Comparable<ActionInfo> {
7176
int compareTo(ActionInfo other) => index.compareTo(other.index);
7277
}
7378

74-
Job _coverageCompletionJob() => Job(
79+
Job _coverageCompletionJob(RootConfig rootConfig) => Job(
7580
name: 'Mark Coveralls job finished',
7681
runsOn: 'ubuntu-latest',
7782
steps: [
@@ -82,6 +87,7 @@ Job _coverageCompletionJob() => Job(
8287
'github-token': r'${{ secrets.GITHUB_TOKEN }}',
8388
'parallel-finished': true
8489
},
90+
versionOverrides: rootConfig.existingActionVersions,
8591
)
8692
],
8793
);
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
/// This file is generated, and should not be modified by hand.
6+
///
7+
/// To regenerate it, run the `tool/generate_action_versions.dart` script.
8+
9+
const actionsCacheVersion = '88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8';
10+
const dartLangSetupDartVersion = 'd6a63dab3335f427404425de0fbfed4686d93c4f';
11+
const actionsCheckoutVersion = '8e5e7e5ab8b370d6c329ec480221332ada57f0ab';
12+
const subositoFlutterActionVersion = '48cafc24713cca54bbe03cdc3a423187d413aafa';
13+
const coverallsappGithubActionVersion = 'master';
14+
const codecovCodecovActionVersion = 'main';

mono_repo/lib/src/commands/github/generate.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ import '../../root_config.dart';
1212
import '../../user_exception.dart';
1313
import 'github_yaml.dart';
1414

15+
const dependabotFileNames = [
16+
'.github/dependabot.yaml',
17+
'.github/dependabot.yml',
18+
];
19+
1520
void generateGitHubActions(
1621
RootConfig rootConfig, {
1722
bool validateOnly = false,

mono_repo/lib/src/commands/github/github_yaml.dart

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ Map<String, String> generateGitHubYml(RootConfig rootConfig) {
122122
Map.fromEntries(allJobs.map((e) => MapEntry(e.id, e.value)));
123123

124124
for (var completion in completionMap.entries) {
125-
final job = completion.key.completionJobFactory!()
125+
final job = completion.key.completionJobFactory!(rootConfig)
126126
..needs = completion.value.toList();
127127

128128
jobList['job_${jobList.length + 1}'] = job;
@@ -204,7 +204,10 @@ Iterable<_MapEntryWithStage> _listJobs(
204204

205205
for (var job in jobs) {
206206
if (job is _SelfValidateJob) {
207-
yield jobEntry(_selfValidateJob(rootConfig.monoConfig), job.stageName);
207+
yield jobEntry(
208+
_selfValidateJob(rootConfig.monoConfig, rootConfig),
209+
job.stageName,
210+
);
208211
continue;
209212
}
210213

@@ -340,6 +343,7 @@ extension on CIJobEntry {
340343
job.flavor,
341344
job.sdk,
342345
commandEntries,
346+
rootConfig,
343347
config: rootConfig.monoConfig,
344348
additionalCacheKeys: {
345349
'packages': packages.join('-'),
@@ -382,7 +386,8 @@ Job _githubJob(
382386
String runsOn,
383387
PackageFlavor packageFlavor,
384388
String sdkVersion,
385-
List<_CommandEntryBase> runCommands, {
389+
List<_CommandEntryBase> runCommands,
390+
RootConfig rootConfig, {
386391
required BasicConfiguration config,
387392
Map<String, String>? additionalCacheKeys,
388393
}) =>
@@ -393,17 +398,20 @@ Job _githubJob(
393398
if (!runsOn.startsWith('windows'))
394399
_cacheEntries(
395400
runsOn,
401+
rootConfig: rootConfig,
396402
additionalCacheKeys: {
397403
'sdk': sdkVersion,
398404
if (additionalCacheKeys != null) ...additionalCacheKeys,
399405
},
400406
),
401-
packageFlavor.setupStep(sdkVersion),
407+
packageFlavor.setupStep(sdkVersion, rootConfig),
402408
..._beforeSteps(runCommands.whereType<_CommandEntry>()),
403409
ActionInfo.checkout.usage(
404410
id: 'checkout',
411+
versionOverrides: rootConfig.existingActionVersions,
405412
),
406-
for (var command in runCommands) ...command.runContent(config),
413+
for (var command in runCommands)
414+
...command.runContent(config, rootConfig),
407415
],
408416
);
409417

@@ -424,7 +432,7 @@ class _CommandEntryBase {
424432

425433
_CommandEntryBase(this.name, this.run);
426434

427-
Iterable<Step> runContent(BasicConfiguration config) =>
435+
Iterable<Step> runContent(BasicConfiguration config, RootConfig rootConfig) =>
428436
[Step.run(name: name, run: run)];
429437
}
430438

@@ -444,15 +452,16 @@ class _CommandEntry extends _CommandEntryBase {
444452
});
445453

446454
@override
447-
Iterable<Step> runContent(BasicConfiguration config) => [
455+
Iterable<Step> runContent(BasicConfiguration config, RootConfig rootConfig) =>
456+
[
448457
Step.run(
449458
id: id,
450459
name: name,
451460
ifContent: ifCondition,
452461
workingDirectory: workingDirectory,
453462
run: run,
454463
),
455-
...?type?.afterEachSteps(workingDirectory, config),
464+
...?type?.afterEachSteps(workingDirectory, config, rootConfig),
456465
];
457466
}
458467

@@ -464,6 +473,7 @@ class _CommandEntry extends _CommandEntryBase {
464473
/// store and retrieve the cache.
465474
Step _cacheEntries(
466475
String runsOn, {
476+
required RootConfig rootConfig,
467477
Map<String, String>? additionalCacheKeys,
468478
}) {
469479
final cacheKeyParts = [
@@ -490,6 +500,7 @@ Step _cacheEntries(
490500
'key': restoreKeys.first,
491501
'restore-keys': restoreKeys.skip(1).join('\n'),
492502
},
503+
versionOverrides: rootConfig.existingActionVersions,
493504
);
494505
}
495506

@@ -500,7 +511,8 @@ String _maxLength(String input) {
500511
return input.substring(0, 512 - hash.length) + hash;
501512
}
502513

503-
Job _selfValidateJob(BasicConfiguration config) => _githubJob(
514+
Job _selfValidateJob(BasicConfiguration config, RootConfig rootConfig) =>
515+
_githubJob(
504516
selfValidateJobName,
505517
_ubuntuLatest,
506518
PackageFlavor.dart,
@@ -509,6 +521,7 @@ Job _selfValidateJob(BasicConfiguration config) => _githubJob(
509521
for (var command in selfValidateCommands)
510522
_CommandEntryBase(selfValidateJobName, command),
511523
],
524+
rootConfig,
512525
config: config,
513526
);
514527

@@ -537,16 +550,18 @@ class _MapEntryWithStage {
537550
}
538551

539552
extension on PackageFlavor {
540-
Step setupStep(String sdkVersion) {
553+
Step setupStep(String sdkVersion, RootConfig rootConfig) {
541554
switch (this) {
542555
case PackageFlavor.dart:
543556
return ActionInfo.setupDart.usage(
544557
withContent: {'sdk': sdkVersion},
558+
versionOverrides: rootConfig.existingActionVersions,
545559
);
546560

547561
case PackageFlavor.flutter:
548562
return ActionInfo.setupFlutter.usage(
549563
withContent: {'channel': sdkVersion},
564+
versionOverrides: rootConfig.existingActionVersions,
550565
);
551566
}
552567
}

mono_repo/lib/src/root_config.dart

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ import 'dart:io';
77

88
import 'package:path/path.dart' as p;
99
import 'package:pubspec_parse/pubspec_parse.dart';
10+
import 'package:yaml/yaml.dart';
1011

12+
import 'commands/github/generate.dart';
13+
import 'commands/github/github_yaml.dart';
1114
import 'mono_config.dart';
1215
import 'package_config.dart';
1316
import 'user_exception.dart';
@@ -60,6 +63,7 @@ class RootConfig extends ListBase<PackageConfig> {
6063
final String rootDirectory;
6164
final MonoConfig monoConfig;
6265
final List<PackageConfig> _configs;
66+
final Map<String, String>? existingActionVersions;
6367

6468
factory RootConfig({String? rootDirectory, bool recursive = true}) {
6569
rootDirectory ??= p.current;
@@ -94,14 +98,35 @@ class RootConfig extends ListBase<PackageConfig> {
9498
);
9599
}
96100

101+
// If a dependabot configuration file exists, assume the action versions in
102+
// the generated workflow file are maintained by dependabot; parse and use
103+
// those versions.
104+
Map<String, String>? existingActionVersions;
105+
final hasDependabot = dependabotFileNames
106+
.map((name) => File(p.join(rootDirectory!, name)))
107+
.any((file) => file.existsSync());
108+
final githubWorkflowFile =
109+
File(p.join(rootDirectory, defaultGitHubWorkflowFilePath));
110+
if (hasDependabot && githubWorkflowFile.existsSync()) {
111+
existingActionVersions = parseActionVersions(
112+
githubWorkflowFile.readAsStringSync(),
113+
);
114+
}
115+
97116
return RootConfig._(
98117
rootDirectory,
99118
MonoConfig.fromRepo(rootDirectory: rootDirectory),
100119
configs,
120+
existingActionVersions,
101121
);
102122
}
103123

104-
RootConfig._(this.rootDirectory, this.monoConfig, this._configs);
124+
RootConfig._(
125+
this.rootDirectory,
126+
this.monoConfig,
127+
this._configs,
128+
this.existingActionVersions,
129+
);
105130

106131
@override
107132
int get length => _configs.length;
@@ -116,4 +141,42 @@ class RootConfig extends ListBase<PackageConfig> {
116141
@override
117142
void operator []=(int index, PackageConfig pkg) =>
118143
throw UnsupportedError('This List is read-only.');
144+
145+
/// Parse any github action versions from a workflow file.
146+
///
147+
/// This returns a map of <action name> to <action version>.
148+
static Map<String, String> parseActionVersions(String yamlText) {
149+
// "dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d"
150+
final usageRegex = RegExp(r'([\w\.-]+)\/([\w\.-]+)@([\w\.]+)');
151+
152+
final yaml = loadYaml(yamlText);
153+
final result = <String, String>{};
154+
155+
void collect(dynamic yaml) {
156+
if (yaml is List) {
157+
for (var item in yaml) {
158+
collect(item);
159+
}
160+
} else if (yaml is Map) {
161+
const usesKey = 'uses';
162+
163+
if (yaml.containsKey(usesKey)) {
164+
// dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d
165+
final usage = yaml[usesKey] as String;
166+
final match = usageRegex.firstMatch(usage);
167+
if (match != null) {
168+
result['${match.group(1)}/${match.group(2)}'] = match.group(3)!;
169+
}
170+
}
171+
172+
for (var item in yaml.entries) {
173+
collect(item.value);
174+
}
175+
}
176+
}
177+
178+
collect(yaml);
179+
180+
return result;
181+
}
119182
}

0 commit comments

Comments
 (0)