Skip to content

Commit 3d5da4c

Browse files
authored
fix: add xml 7.x support (#761) (#764)
## What does this change? Fixes #761. `flutter_gen_core` pinned `xml: ^6.0.0`, so any project depending on both `flutter_gen`/`flutter_gen_runner` and `xml: ^7.0.0` failed version solving. This widens the constraint to `xml: '>=6.0.0 <8.0.0'` instead of bumping to `^7.0.0`, deliberately: - **Non-breaking.** Consumers on xml 7.x (and Dart ≥ 3.11) now resolve cleanly, while consumers on older toolchains keep working — pub falls back to xml 6.x automatically. A hard `^7.0.0` would instead *force* everyone onto xml 7, `image ≥ 4.9.x`, and effectively Dart ≥ 3.11 (xml 7 requires `sdk: ^3.11.0`), which would also require bumping this package's own `sdk` floor. - **Robust.** The range gives the resolver a fallback (xml 6.x + `image` 4.8.x), so it stays solvable through transitive `image`/`meta`/Flutter-SDK pin shifts that a single forced major can't survive. Also replaces the deprecated `XmlNode.text` with `XmlNode.innerText`. This is cleanup, not required to build: `XmlNode.text` is only *deprecated* in xml 7.x (deprecated starting from 6.3.0), not removed, and the call was already `// ignore`-suppressed. `innerText` has existed since xml 6.0.0, so it's source-compatible with both majors and future-proofs against an eventual removal in xml 8.x. To guard both majors going forward, adds a cross-platform Dart matrix (`scripts/test_xml_versions.dart`, exposed as `melos test:xml-matrix`) that runs `flutter_gen_core`'s tests against both xml 6.x and 7.x via a temporary `dependency_overrides`, plus a CI step that runs it on every PR. Fixes #761 🎯 ## Type of change - [x] Bug fix (non-breaking change which fixes an issue)
1 parent 89c1920 commit 3d5da4c

5 files changed

Lines changed: 144 additions & 3 deletions

File tree

.github/workflows/build.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ jobs:
4444
if: startsWith(matrix.os, 'ubuntu')
4545
run: melos test:dart --no-select
4646

47+
- name: Test flutter_gen_core against both xml majors (6.x and 7.x)
48+
if: startsWith(matrix.os, 'ubuntu')
49+
run: melos test:xml-matrix
50+
4751
- name: Run tests for Flutter packages
4852
if: startsWith(matrix.os, 'ubuntu')
4953
run: melos test:flutter --no-select

melos.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ scripts:
122122
bash ./scripts/coverage.sh packages/core
123123
description: bash ./scripts/coverage.sh packages/core
124124

125+
test:xml-matrix:
126+
run: dart scripts/test_xml_versions.dart
127+
description: Run flutter_gen_core tests against both supported xml majors (6.x and 7.x)
128+
125129
publish:force:
126130
run: dart pub publish -f
127131
exec:

packages/core/lib/generators/colors_generator.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,7 @@ class _Color {
106106
_Color.fromXmlElement(XmlElement element)
107107
: this(
108108
element.getAttribute('name')!,
109-
// ignore: deprecated_member_use
110-
element.text,
109+
element.innerText,
111110
element.getAttribute('type')?.split(' ') ?? List.empty(),
112111
);
113112

packages/core/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ dependencies:
1717
path: ^1.8.0
1818
yaml: ^3.0.0
1919
mime: '>=1.0.0 <3.0.0'
20-
xml: ^6.0.0
20+
xml: '>=6.0.0 <8.0.0'
2121
dartx: ^1.0.0
2222
color: ^3.0.0
2323
collection: ^1.15.0

scripts/test_xml_versions.dart

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// Verify flutter_gen_core works against every supported major `xml` version.
2+
//
3+
// The package declares `xml: '>=6.0.0 <8.0.0'`, but a normal `dart pub get`
4+
// only ever resolves one major: `image` (a transitive dependency) pins `xml`,
5+
// so its newest releases force `xml` 7.x. As a result the normal `melos test`
6+
// never exercises the 6.x line.
7+
//
8+
// This script pins each major in turn via a temporary `pubspec_overrides.yaml`
9+
// (gitignored) and runs the core test suite against it, so a regression on
10+
// either xml 6.x or 7.x is caught.
11+
//
12+
// The 6.x pass also pins `image` below 4.9.0: `image` 4.9.0+ requires `xml`
13+
// ^7.0.1, so a downstream package constrained to `xml` 6.x resolves `image`
14+
// to its last 6.x-compatible release (4.8.0). Pinning only `xml` would instead
15+
// leave the newest `image` overridden onto an incompatible `xml` and fail to
16+
// compile, which is not a real resolution any consumer could hit.
17+
//
18+
// It is pure Dart (only `dart:io`) so it runs identically on Linux, macOS and
19+
// Windows. The xml 7.x pass requires Dart >= 3.11; on older toolchains it is
20+
// skipped with a warning instead of failing.
21+
//
22+
// Run via `melos test:xml-matrix` or `dart scripts/test_xml_versions.dart`.
23+
24+
import 'dart:io';
25+
26+
Future<void> main() async {
27+
final coreDir = Directory('${_repoRoot().path}/packages/core');
28+
final override = File('${coreDir.path}/pubspec_overrides.yaml');
29+
30+
// Always remove the temporary override and re-resolve so the working tree
31+
// and lockfile are left in their default state, even if a pass fails.
32+
void cleanup() {
33+
if (override.existsSync()) override.deleteSync();
34+
Process.runSync(
35+
Platform.resolvedExecutable,
36+
['pub', 'get'],
37+
workingDirectory: coreDir.path,
38+
);
39+
}
40+
41+
try {
42+
// image 4.9.0+ requires xml ^7.0.1, so the xml 6.x pass must also pin
43+
// image to its last 6.x-compatible release. This mirrors what pub resolves
44+
// for a downstream package constrained to xml 6.x.
45+
await _runPass(
46+
'^6.0.0',
47+
coreDir,
48+
override,
49+
extraOverrides: {'image': "'>=4.5.4 <4.9.0'"},
50+
);
51+
52+
final dart = _dartVersion();
53+
if (dart.major > 3 || (dart.major == 3 && dart.minor >= 11)) {
54+
await _runPass('^7.0.0', coreDir, override);
55+
} else {
56+
stdout.writeln('\n⚠️ Skipping xml 7.x pass: requires Dart >= 3.11 '
57+
'(found ${dart.major}.${dart.minor}).');
58+
}
59+
} catch (error) {
60+
cleanup();
61+
stderr.writeln('\n❌ $error');
62+
exit(1);
63+
}
64+
65+
cleanup();
66+
stdout
67+
.writeln('\n✅ flutter_gen_core passed against all checked xml versions.');
68+
}
69+
70+
Future<void> _runPass(
71+
String constraint,
72+
Directory coreDir,
73+
File override, {
74+
Map<String, String> extraOverrides = const {},
75+
}) async {
76+
stdout.writeln('\n========================================================');
77+
stdout.writeln(' flutter_gen_core test pass — pinning xml: $constraint');
78+
stdout.writeln('========================================================');
79+
final overrides = StringBuffer('dependency_overrides:\n xml: $constraint\n');
80+
extraOverrides.forEach((name, c) => overrides.writeln(' $name: $c'));
81+
override.writeAsStringSync(overrides.toString());
82+
await _dart(['pub', 'get'], coreDir);
83+
stdout.writeln('-> resolved xml ${_resolvedXmlVersion(coreDir)}');
84+
await _dart(['test'], coreDir);
85+
}
86+
87+
/// Runs `dart <args>` in [cwd], streaming output, and throws on failure.
88+
Future<void> _dart(List<String> args, Directory cwd) async {
89+
final process = await Process.start(
90+
Platform.resolvedExecutable,
91+
args,
92+
workingDirectory: cwd.path,
93+
mode: ProcessStartMode.inheritStdio,
94+
);
95+
final code = await process.exitCode;
96+
if (code != 0) {
97+
throw 'dart ${args.join(' ')} failed (exit $code) in ${cwd.path}';
98+
}
99+
}
100+
101+
/// Parses the running Dart SDK version, e.g. "3.11.3 (stable) (...)".
102+
({int major, int minor}) _dartVersion() {
103+
final parts = Platform.version.split(' ').first.split('.');
104+
return (major: int.parse(parts[0]), minor: int.parse(parts[1]));
105+
}
106+
107+
/// Reads the resolved `xml` version out of `packages/core/pubspec.lock`.
108+
String _resolvedXmlVersion(Directory coreDir) {
109+
final lock = File('${coreDir.path}/pubspec.lock');
110+
if (!lock.existsSync()) return 'unknown';
111+
final lines = lock.readAsLinesSync();
112+
for (var i = 0; i < lines.length; i++) {
113+
if (lines[i].trimRight() == ' xml:') {
114+
for (var j = i + 1;
115+
j < lines.length && lines[j].startsWith(' ');
116+
j++) {
117+
final match = RegExp(r'version:\s*"?([^"\s]+)"?').firstMatch(lines[j]);
118+
if (match != null) return match.group(1)!;
119+
}
120+
}
121+
}
122+
return 'unknown';
123+
}
124+
125+
/// Locates the melos workspace root by walking up from this script's location,
126+
/// falling back to the current directory (melos runs scripts from the root).
127+
Directory _repoRoot() {
128+
var dir = File.fromUri(Platform.script).parent;
129+
while (dir.path != dir.parent.path) {
130+
if (File('${dir.path}/melos.yaml').existsSync()) return dir;
131+
dir = dir.parent;
132+
}
133+
return Directory.current;
134+
}

0 commit comments

Comments
 (0)