Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

parse action versions out of workflow files #420

Merged
merged 26 commits into from
May 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
789cc4c
parse action versions out of workflow files
devoncarew Oct 7, 2022
d63eabf
use '–' instead of '-'
devoncarew Oct 7, 2022
301e319
revert unicode char changes
devoncarew Oct 7, 2022
543fc45
add test; update readme
devoncarew Oct 9, 2022
ad53f87
merge to master
devoncarew May 22, 2023
9744389
support multiple dependabot file names
devoncarew May 22, 2023
f39c6b2
review feedback
devoncarew May 22, 2023
0e9f580
generate the internal action versions from the current ones used in t…
jakemac53 May 23, 2023
6cb4749
add a note that the file is generated, and how to regenerate it
jakemac53 May 23, 2023
7cf68c5
format as a part of the command
jakemac53 May 23, 2023
45b3396
log the old and new content
jakemac53 May 23, 2023
8f4d2b8
refactor, fix obvious broken code
jakemac53 May 23, 2023
bb43afa
and actually log the output from the test
jakemac53 May 23, 2023
da65da7
check the format result, error if it errors and forward output
jakemac53 May 23, 2023
ceb291a
wrap old/new content in backticks, dont write extra newline
jakemac53 May 24, 2023
ae202fa
restructure code a bit and fix bug, regenerate file
jakemac53 May 24, 2023
b1075de
only test generated code on linux
jakemac53 May 24, 2023
5324c15
Merge pull request #12 from google/gen-versions
devoncarew May 24, 2023
d4430c1
misc changes
devoncarew May 24, 2023
9683450
add a test that validate can pass if versions dont match but dependab…
jakemac53 May 25, 2023
6452c95
fix analyzer diagnostics
jakemac53 May 25, 2023
c7ea02d
fix tests I broke
jakemac53 May 25, 2023
28d0175
Merge pull request #13 from google/add-test
devoncarew May 25, 2023
29cad2f
update pubspec and changelog
devoncarew May 25, 2023
42adeb4
update version
devoncarew May 25, 2023
dd4300b
regenerate
devoncarew May 25, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/dart.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions mono_repo/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 6.5.7

- Updated `mono_repo` to use the existing action versions from the generated
workflow file when dependabot is configured for the repo.

## 6.5.6

- Restore the previous ordering behavior for jobs by using a secondary sort
Expand Down
15 changes: 15 additions & 0 deletions mono_repo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,5 +228,20 @@ Look at these repositories for examples of `mono_repo` usage:
* https://github.com/dart-lang/webdev
* https://github.com/google/json_serializable.dart

## Mono_repo and Dependabot

Historically, package:mono_repo and Dependabot couldn't be used together -
they both wanted to maintain your GitHub workflow file. We've adopted mono_repo
so that you can now use both it and Dependabot in your repo.

When generating your workflow configuration (`mono_repo generate`) mono_repo
will write out its current default action versions into the workflow file. If
however it sees that the repo has a Dependabot configuration - has a file named
`.github/dependabot.yaml` in the repo - mono_repo will instead parse the
workflow file, read out the current action versions, and use those versions when
re-generating the file. This lets mono_repo manage the overall structure of the
file, while allowing Dependabot to independently move various action versions
forward as necessary.

[Dart packages]: https://dart.dev/guides/libraries/create-library-packages
[setup your PATH]: https://dart.dev/tools/pub/cmd/pub-global#running-a-script-from-your-path
24 changes: 15 additions & 9 deletions mono_repo/lib/src/commands/github/action_info.dart
Original file line number Diff line number Diff line change
@@ -1,41 +1,43 @@
import '../../root_config.dart';
import 'action_versions.dart';
import 'job.dart';
import 'step.dart';

enum ActionInfo implements Comparable<ActionInfo> {
cache(
name: 'Cache Pub hosted dependencies',
repo: 'actions/cache',
version: '88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8', // v3.3.1
version: actionsCacheVersion,
),
checkout(
name: 'Checkout repository',
repo: 'actions/checkout',
version: '8e5e7e5ab8b370d6c329ec480221332ada57f0ab', // v3.5.2
version: actionsCheckoutVersion,
),
setupDart(
name: 'Setup Dart SDK',
repo: 'dart-lang/setup-dart',
version: 'd6a63dab3335f427404425de0fbfed4686d93c4f', // v1.5.0
version: dartLangSetupDartVersion,
),
setupFlutter(
name: 'Setup Flutter SDK',
repo: 'subosito/flutter-action',
version: '48cafc24713cca54bbe03cdc3a423187d413aafa', // v2.10.0
version: subositoFlutterActionVersion,
),

/// See https://github.com/marketplace/actions/coveralls-github-action
coveralls(
name: 'Upload coverage to Coveralls',
repo: 'coverallsapp/github-action',
version: 'master',
version: coverallsappGithubActionVersion,
completionJobFactory: _coverageCompletionJob,
),

/// See https://github.com/marketplace/actions/codecov
codecov(
name: 'Upload coverage to codecov.io',
repo: 'codecov/codecov-action',
version: 'main',
version: codecovCodecovActionVersion,
);

const ActionInfo({
Expand All @@ -48,16 +50,19 @@ enum ActionInfo implements Comparable<ActionInfo> {
final String repo;
final String version;
final String name;
final Job Function()? completionJobFactory;
final Job Function(RootConfig rootConfig)? completionJobFactory;

Step usage({
String? name,
String? id,
Map<String, dynamic>? withContent,
Map<String, String>? versionOverrides,
}) {
name ??= this.name;
final useVersion =
(versionOverrides == null ? null : versionOverrides[repo]) ?? version;
final step = Step.uses(
uses: '$repo@$version',
uses: '$repo@$useVersion',
id: id,
name: name,
withContent: withContent,
Expand All @@ -71,7 +76,7 @@ enum ActionInfo implements Comparable<ActionInfo> {
int compareTo(ActionInfo other) => index.compareTo(other.index);
}

Job _coverageCompletionJob() => Job(
Job _coverageCompletionJob(RootConfig rootConfig) => Job(
name: 'Mark Coveralls job finished',
runsOn: 'ubuntu-latest',
steps: [
Expand All @@ -82,6 +87,7 @@ Job _coverageCompletionJob() => Job(
'github-token': r'${{ secrets.GITHUB_TOKEN }}',
'parallel-finished': true
},
versionOverrides: rootConfig.existingActionVersions,
)
],
);
Expand Down
14 changes: 14 additions & 0 deletions mono_repo/lib/src/commands/github/action_versions.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// 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.

/// This file is generated, and should not be modified by hand.
///
/// To regenerate it, run the `tool/generate_action_versions.dart` script.

const actionsCacheVersion = '88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8';
const dartLangSetupDartVersion = 'd6a63dab3335f427404425de0fbfed4686d93c4f';
const actionsCheckoutVersion = '8e5e7e5ab8b370d6c329ec480221332ada57f0ab';
const subositoFlutterActionVersion = '48cafc24713cca54bbe03cdc3a423187d413aafa';
const coverallsappGithubActionVersion = 'master';
const codecovCodecovActionVersion = 'main';
5 changes: 5 additions & 0 deletions mono_repo/lib/src/commands/github/generate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ import '../../root_config.dart';
import '../../user_exception.dart';
import 'github_yaml.dart';

const dependabotFileNames = [
'.github/dependabot.yaml',
'.github/dependabot.yml',
];

void generateGitHubActions(
RootConfig rootConfig, {
bool validateOnly = false,
Expand Down
35 changes: 25 additions & 10 deletions mono_repo/lib/src/commands/github/github_yaml.dart
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ Map<String, String> generateGitHubYml(RootConfig rootConfig) {
Map.fromEntries(allJobs.map((e) => MapEntry(e.id, e.value)));

for (var completion in completionMap.entries) {
final job = completion.key.completionJobFactory!()
final job = completion.key.completionJobFactory!(rootConfig)
..needs = completion.value.toList();

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

for (var job in jobs) {
if (job is _SelfValidateJob) {
yield jobEntry(_selfValidateJob(rootConfig.monoConfig), job.stageName);
yield jobEntry(
_selfValidateJob(rootConfig.monoConfig, rootConfig),
job.stageName,
);
continue;
}

Expand Down Expand Up @@ -340,6 +343,7 @@ extension on CIJobEntry {
job.flavor,
job.sdk,
commandEntries,
rootConfig,
config: rootConfig.monoConfig,
additionalCacheKeys: {
'packages': packages.join('-'),
Expand Down Expand Up @@ -382,7 +386,8 @@ Job _githubJob(
String runsOn,
PackageFlavor packageFlavor,
String sdkVersion,
List<_CommandEntryBase> runCommands, {
List<_CommandEntryBase> runCommands,
RootConfig rootConfig, {
required BasicConfiguration config,
Map<String, String>? additionalCacheKeys,
}) =>
Expand All @@ -393,17 +398,20 @@ Job _githubJob(
if (!runsOn.startsWith('windows'))
_cacheEntries(
runsOn,
rootConfig: rootConfig,
additionalCacheKeys: {
'sdk': sdkVersion,
if (additionalCacheKeys != null) ...additionalCacheKeys,
},
),
packageFlavor.setupStep(sdkVersion),
packageFlavor.setupStep(sdkVersion, rootConfig),
..._beforeSteps(runCommands.whereType<_CommandEntry>()),
ActionInfo.checkout.usage(
id: 'checkout',
versionOverrides: rootConfig.existingActionVersions,
),
for (var command in runCommands) ...command.runContent(config),
for (var command in runCommands)
...command.runContent(config, rootConfig),
],
);

Expand All @@ -424,7 +432,7 @@ class _CommandEntryBase {

_CommandEntryBase(this.name, this.run);

Iterable<Step> runContent(BasicConfiguration config) =>
Iterable<Step> runContent(BasicConfiguration config, RootConfig rootConfig) =>
[Step.run(name: name, run: run)];
}

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

@override
Iterable<Step> runContent(BasicConfiguration config) => [
Iterable<Step> runContent(BasicConfiguration config, RootConfig rootConfig) =>
[
Step.run(
id: id,
name: name,
ifContent: ifCondition,
workingDirectory: workingDirectory,
run: run,
),
...?type?.afterEachSteps(workingDirectory, config),
...?type?.afterEachSteps(workingDirectory, config, rootConfig),
];
}

Expand All @@ -464,6 +473,7 @@ class _CommandEntry extends _CommandEntryBase {
/// store and retrieve the cache.
Step _cacheEntries(
String runsOn, {
required RootConfig rootConfig,
Map<String, String>? additionalCacheKeys,
}) {
final cacheKeyParts = [
Expand All @@ -490,6 +500,7 @@ Step _cacheEntries(
'key': restoreKeys.first,
'restore-keys': restoreKeys.skip(1).join('\n'),
},
versionOverrides: rootConfig.existingActionVersions,
);
}

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

Job _selfValidateJob(BasicConfiguration config) => _githubJob(
Job _selfValidateJob(BasicConfiguration config, RootConfig rootConfig) =>
_githubJob(
selfValidateJobName,
_ubuntuLatest,
PackageFlavor.dart,
Expand All @@ -509,6 +521,7 @@ Job _selfValidateJob(BasicConfiguration config) => _githubJob(
for (var command in selfValidateCommands)
_CommandEntryBase(selfValidateJobName, command),
],
rootConfig,
config: config,
);

Expand Down Expand Up @@ -537,16 +550,18 @@ class _MapEntryWithStage {
}

extension on PackageFlavor {
Step setupStep(String sdkVersion) {
Step setupStep(String sdkVersion, RootConfig rootConfig) {
switch (this) {
case PackageFlavor.dart:
return ActionInfo.setupDart.usage(
withContent: {'sdk': sdkVersion},
versionOverrides: rootConfig.existingActionVersions,
);

case PackageFlavor.flutter:
return ActionInfo.setupFlutter.usage(
withContent: {'channel': sdkVersion},
versionOverrides: rootConfig.existingActionVersions,
);
}
}
Expand Down
65 changes: 64 additions & 1 deletion mono_repo/lib/src/root_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import 'dart:io';

import 'package:path/path.dart' as p;
import 'package:pubspec_parse/pubspec_parse.dart';
import 'package:yaml/yaml.dart';

import 'commands/github/generate.dart';
import 'commands/github/github_yaml.dart';
import 'mono_config.dart';
import 'package_config.dart';
import 'user_exception.dart';
Expand Down Expand Up @@ -60,6 +63,7 @@ class RootConfig extends ListBase<PackageConfig> {
final String rootDirectory;
final MonoConfig monoConfig;
final List<PackageConfig> _configs;
final Map<String, String>? existingActionVersions;

factory RootConfig({String? rootDirectory, bool recursive = true}) {
rootDirectory ??= p.current;
Expand Down Expand Up @@ -94,14 +98,35 @@ class RootConfig extends ListBase<PackageConfig> {
);
}

// If a dependabot configuration file exists, assume the action versions in
// the generated workflow file are maintained by dependabot; parse and use
// those versions.
Map<String, String>? existingActionVersions;
final hasDependabot = dependabotFileNames
.map((name) => File(p.join(rootDirectory!, name)))
.any((file) => file.existsSync());
final githubWorkflowFile =
File(p.join(rootDirectory, defaultGitHubWorkflowFilePath));
if (hasDependabot && githubWorkflowFile.existsSync()) {
existingActionVersions = parseActionVersions(
githubWorkflowFile.readAsStringSync(),
);
}

return RootConfig._(
rootDirectory,
MonoConfig.fromRepo(rootDirectory: rootDirectory),
configs,
existingActionVersions,
);
}

RootConfig._(this.rootDirectory, this.monoConfig, this._configs);
RootConfig._(
this.rootDirectory,
this.monoConfig,
this._configs,
this.existingActionVersions,
);

@override
int get length => _configs.length;
Expand All @@ -116,4 +141,42 @@ class RootConfig extends ListBase<PackageConfig> {
@override
void operator []=(int index, PackageConfig pkg) =>
throw UnsupportedError('This List is read-only.');

/// Parse any github action versions from a workflow file.
///
/// This returns a map of <action name> to <action version>.
static Map<String, String> parseActionVersions(String yamlText) {
// "dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d"
final usageRegex = RegExp(r'([\w\.-]+)\/([\w\.-]+)@([\w\.]+)');

final yaml = loadYaml(yamlText);
final result = <String, String>{};

void collect(dynamic yaml) {
if (yaml is List) {
for (var item in yaml) {
collect(item);
}
} else if (yaml is Map) {
const usesKey = 'uses';

if (yaml.containsKey(usesKey)) {
// dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d
final usage = yaml[usesKey] as String;
final match = usageRegex.firstMatch(usage);
if (match != null) {
result['${match.group(1)}/${match.group(2)}'] = match.group(3)!;
}
}

for (var item in yaml.entries) {
collect(item.value);
}
}
}

collect(yaml);

return result;
}
}
Loading