Skip to content

Commit

Permalink
fix: Remove old pubspec_overrides entries on clean (#861)
Browse files Browse the repository at this point in the history
<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->

## Description

To make the migration easier this PR brings back support for removing
previously Melos generated pubspec_overrides entries.

It also adds some clearer migration instructions.

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [ ] ✨ `feat` -- New feature (non-breaking change which adds
functionality)
- [x] 🛠️ `fix` -- Bug fix (non-breaking change which fixes an issue)
- [ ] ❌ `!` -- Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 `refactor` -- Code refactor
- [ ] ✅ `ci` -- Build configuration change
- [ ] 📝 `docs` -- Documentation
- [ ] 🗑️ `chore` -- Chore
  • Loading branch information
spydon authored Feb 3, 2025
1 parent 303883b commit d5a6ea6
Show file tree
Hide file tree
Showing 3 changed files with 222 additions and 8 deletions.
28 changes: 20 additions & 8 deletions packages/melos/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,26 @@ Since the [pub workspaces](https://dart.dev/tools/pub/workspaces) feature has
been released, Melos has been updated to rely on that, instead of creating
`pubspec_overrides.yaml` files and thus some migration is needed.

The main difference is that:
1. There is no longer a `melos.yaml` file, only the root `pubspec.yaml`
2. You now have to add `resolution: workspace` to all of your packages'
`pubspec.yaml` files.
3. You now have to add a list of all your packages to the root `pubspec.yaml`
file.

After the migration your root `pubspec.yaml` file would now look something like this:
The main difference for migration is that the `melos.yaml` file no longer
exists, only the root `pubspec.yaml` file.

To migrate to Melos 7.x.x a few steps are needed:
1. Start with running `melos clean` to remove all the `pubspec_overrides.yaml`
entries and then continue with moving all your content.
2. Add `resolution: workspace` to all of your packages' `pubspec.yaml` files.
3. Add a list of all your packages to the root `pubspec.yaml` file, under the
`workspace` key.
4. Move all the content from your `melos.yaml` file to the root `pubspec.yaml`
file, under the `melos` key. (Note that the `packages` list is no longer
needed as it is replaced with the `workspace` list.)

> [!NOTE]
> The `workspace` list doesn't support globs yet, so you have to list all your
> packages manually. Give a thumbs up [here](https://github.com/dart-lang/pub/issues/4391)
> so that the team can prioritize this feature.
After the migration, your root `pubspec.yaml` file would now look something
like this:
```yaml
name: my_workspace
publish_to: none
Expand Down
200 changes: 200 additions & 0 deletions packages/melos/lib/src/commands/clean.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,22 @@ mixin _CleanMixin on _Melos {
logger.warning('Failed to delete $path: $error');
}
}

// Remove any old Melos generated dependency overrides from
// `pubspec_overrides.yaml`. This can be removed after a few versions when
// everyone has migrated to Melos ^7.0.0.
final pubspecOverridesFile = p.join(package.path, 'pubspec_overrides.yaml');
if (fileExists(pubspecOverridesFile)) {
final contents = await readTextFileAsync(pubspecOverridesFile);
final updatedContents = _mergeMelosPubspecOverrides({}, contents);
if (updatedContents != null) {
if (updatedContents.isEmpty) {
deleteEntry(pubspecOverridesFile);
} else {
await writeTextFileAsync(pubspecOverridesFile, updatedContents);
}
}
}
}

Future<void> cleanIntelliJ(MelosWorkspace workspace) async {
Expand All @@ -60,3 +76,187 @@ mixin _CleanMixin on _Melos {
}
}
}

/// Merges the [melosDependencyOverrides] for other workspace packages into the
/// `pubspec_overrides.yaml` file for a package.
///
/// [melosDependencyOverrides] must contain a mapping of workspace package names
/// to their paths relative to the package.
///
/// [pubspecOverridesContent] are the current contents of the package's
/// `pubspec_overrides.yaml` and may be `null` if the file does not exist.
///
/// Whitespace and comments in an existing `pubspec_overrides.yaml` file are
/// preserved.
///
/// Dependency overrides for a melos workspace package that have not been added
/// by melos are not changed or removed. To mark a dependency override as being
/// managed by melos, it is added to marker comment when first added by this
/// function:
///
/// ```yaml
/// # melos_managed_dependency_overrides: a
/// dependency_overrides:
/// a:
/// path: ../a
/// ```
///
/// This function also takes care of removing any dependency overrides that are
/// obsolete from `dependency_overrides` and the marker comment.
String? _mergeMelosPubspecOverrides(
Map<String, Dependency> melosDependencyOverrides,
String? pubspecOverridesContent,
) {
const managedDependencyOverridesMarker = 'melos_managed_dependency_overrides';
final managedDependencyOverridesRegex = RegExp(
'^# $managedDependencyOverridesMarker: (.*)\n',
multiLine: true,
);
final pubspecOverridesEditor = YamlEditor(pubspecOverridesContent ?? '');
final pubspecOverrides = pubspecOverridesEditor
.parseAt([], orElse: () => wrapAsYamlNode(null)).value as Object?;

final dependencyOverrides = pubspecOverridesContent?.isEmpty ?? true
? null
: PubspecOverrides.parse(pubspecOverridesContent!).dependencyOverrides;
final currentManagedDependencyOverrides = managedDependencyOverridesRegex
.firstMatch(pubspecOverridesContent ?? '')
?.group(1)
?.split(',')
.toSet() ??
{};
final newManagedDependencyOverrides = {...currentManagedDependencyOverrides};

if (dependencyOverrides != null) {
for (final dependencyOverride in dependencyOverrides.entries.toList()) {
final packageName = dependencyOverride.key;

if (currentManagedDependencyOverrides.contains(packageName)) {
// This dependency override is managed by melos and might need to be
// updated.

if (melosDependencyOverrides.containsKey(packageName)) {
// Update changed dependency override.
final currentRef = dependencyOverride.value;
final newRef = melosDependencyOverrides[packageName];
if (currentRef != newRef) {
pubspecOverridesEditor.update(
['dependency_overrides', packageName],
wrapAsYamlNode(
newRef!.toJson(),
collectionStyle: CollectionStyle.BLOCK,
),
);
}
} else {
// Remove obsolete dependency override.
pubspecOverridesEditor.remove(['dependency_overrides', packageName]);
dependencyOverrides.remove(packageName);
newManagedDependencyOverrides.remove(packageName);
}
}

// Remove this dependency from the list of workspace dependency overrides,
// so we only add new overrides later on.
melosDependencyOverrides.remove(packageName);
}
}

if (melosDependencyOverrides.isNotEmpty) {
// Now melosDependencyOverrides only contains new dependencies that need to
// be added to the `pubspec_overrides.yaml` file.

newManagedDependencyOverrides.addAll(melosDependencyOverrides.keys);

if (pubspecOverrides == null) {
pubspecOverridesEditor.update(
[],
wrapAsYamlNode(
{
'dependency_overrides': {
for (final dependencyOverride in melosDependencyOverrides.entries)
dependencyOverride.key: dependencyOverride.value.toJson(),
},
},
collectionStyle: CollectionStyle.BLOCK,
),
);
} else {
if (dependencyOverrides == null) {
pubspecOverridesEditor.update(
['dependency_overrides'],
wrapAsYamlNode(
{
for (final dependencyOverride in melosDependencyOverrides.entries)
dependencyOverride.key: dependencyOverride.value.toJson(),
},
collectionStyle: CollectionStyle.BLOCK,
),
);
} else {
for (final dependencyOverride in melosDependencyOverrides.entries) {
pubspecOverridesEditor.update(
['dependency_overrides', dependencyOverride.key],
wrapAsYamlNode(
dependencyOverride.value.toJson(),
collectionStyle: CollectionStyle.BLOCK,
),
);
}
}
}
} else {
// No dependencies need to be added to the `pubspec_overrides.yaml` file.
// This means it is possible that dependency_overrides and/or
// melos_managed_dependency_overrides are now empty.
if (dependencyOverrides?.isEmpty ?? false) {
pubspecOverridesEditor.remove(['dependency_overrides']);
}
}

if (pubspecOverridesEditor.edits.isNotEmpty) {
var result = pubspecOverridesEditor.toString();

// The changes to the `pubspec_overrides.yaml` file might require a change
// in the managed dependencies marker comment.
final setOfManagedDependenciesChanged =
!const DeepCollectionEquality.unordered().equals(
currentManagedDependencyOverrides,
newManagedDependencyOverrides,
);
if (setOfManagedDependenciesChanged) {
if (newManagedDependencyOverrides.isEmpty) {
// When there are no managed dependencies, remove the marker comment.
result = result.replaceAll(managedDependencyOverridesRegex, '');
} else {
if (!managedDependencyOverridesRegex.hasMatch(result)) {
// When there is no marker comment, add one.
result = '# $managedDependencyOverridesMarker: '
'${newManagedDependencyOverrides.join(',')}\n$result';
} else {
// When there is a marker comment, update it.
result = result.replaceFirstMapped(
managedDependencyOverridesRegex,
(match) => '# $managedDependencyOverridesMarker: '
'${newManagedDependencyOverrides.join(',')}\n',
);
}
}
}

if (result.trim() == '{}') {
// YamlEditor uses an empty dictionary ({}) when all properties have been
// removed and the file is essentially empty.
return '';
}

// Make sure the `pubspec_overrides.yaml` file always ends with a newline.
if (result.isEmpty || !result.endsWith('\n')) {
result += '\n';
}

return result;
} else {
return null;
}
}
2 changes: 2 additions & 0 deletions packages/melos/lib/src/commands/runner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'dart:math';
import 'package:ansi_styles/ansi_styles.dart';
import 'package:async/async.dart';
import 'package:cli_util/cli_logging.dart';
import 'package:collection/collection.dart';
import 'package:file/local.dart';
import 'package:glob/glob.dart';
import 'package:meta/meta.dart';
Expand Down Expand Up @@ -34,6 +35,7 @@ import '../common/io.dart';
import '../common/pending_package_update.dart';
import '../common/persistent_shell.dart';
import '../common/platform.dart';
import '../common/pubspec_overrides.dart';
import '../common/utils.dart' as utils;
import '../common/utils.dart';
import '../common/versioning.dart' as versioning;
Expand Down

0 comments on commit d5a6ea6

Please sign in to comment.