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

fix: Remove old pubspec_overrides entries on clean #861

Merged
merged 1 commit into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
Loading