feat: don't reformat AndroidManifest.xml (#3490)
This commit is contained in:
+29
-6
@@ -90,12 +90,35 @@ The command you are running must be run within a Flutter app project that suppor
|
||||
}
|
||||
|
||||
void _addInternetPermissionToFile(String path) {
|
||||
final xmlDocument = XmlDocument.parse(File(path).readAsStringSync());
|
||||
xmlDocument.rootElement.children.add(
|
||||
XmlElement(XmlName('uses-permission'), [
|
||||
XmlAttribute(XmlName('android:name'), 'android.permission.INTERNET'),
|
||||
]),
|
||||
final file = File(path);
|
||||
final contents = file.readAsStringSync();
|
||||
// Insert the permission after the opening <manifest> tag to preserve
|
||||
// the existing formatting of the file.
|
||||
final manifestTagEnd = RegExp('<manifest[^>]*>');
|
||||
final match = manifestTagEnd.firstMatch(contents);
|
||||
if (match == null) return;
|
||||
final indent = _detectIndent(contents);
|
||||
final permissionLine =
|
||||
'$indent<uses-permission android:name="android.permission.INTERNET"/>';
|
||||
final updated = contents.replaceRange(
|
||||
match.end,
|
||||
match.end,
|
||||
'\n$permissionLine',
|
||||
);
|
||||
File(path).writeAsStringSync(xmlDocument.toXmlString(pretty: true));
|
||||
file.writeAsStringSync(updated);
|
||||
}
|
||||
|
||||
/// Detects the indentation used in the manifest by looking at the first
|
||||
/// indented line after the `<manifest>` tag.
|
||||
String _detectIndent(String contents) {
|
||||
final lines = contents.split('\n');
|
||||
for (final line in lines) {
|
||||
final stripped = line.trimLeft();
|
||||
if (stripped.isEmpty || stripped.startsWith('<manifest')) continue;
|
||||
if (line.length != stripped.length) {
|
||||
return line.substring(0, line.length - stripped.length);
|
||||
}
|
||||
}
|
||||
return ' ';
|
||||
}
|
||||
}
|
||||
|
||||
+124
@@ -35,6 +35,55 @@ void main() {
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="dev.shorebird.u_shorebird_clock">
|
||||
</manifest>
|
||||
''';
|
||||
|
||||
// A stock AndroidManifest.xml from `flutter create`.
|
||||
const stockFlutterManifest = r'''
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application
|
||||
android:label="xml"
|
||||
android:name="\${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:taskAffinity=""
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
<!-- Required to query activities that can process text, see:
|
||||
https://developer.android.com/training/package-visibility and
|
||||
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||
|
||||
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
''';
|
||||
|
||||
group(AndroidInternetPermissionValidator, () {
|
||||
@@ -239,6 +288,81 @@ void main() {
|
||||
);
|
||||
expect(results, isEmpty);
|
||||
});
|
||||
|
||||
test('preserves formatting of stock Flutter manifest', () async {
|
||||
final manifestPath = p.join(
|
||||
projectRoot.path,
|
||||
'android',
|
||||
'app',
|
||||
'src',
|
||||
'main',
|
||||
);
|
||||
writeManifestToPath(stockFlutterManifest, manifestPath);
|
||||
|
||||
final results = await runWithOverrides(
|
||||
AndroidInternetPermissionValidator().validate,
|
||||
);
|
||||
expect(results, hasLength(1));
|
||||
await runWithOverrides(() => results.first.fix!());
|
||||
|
||||
final updated = File(
|
||||
p.join(manifestPath, 'AndroidManifest.xml'),
|
||||
).readAsStringSync();
|
||||
final originalLines = stockFlutterManifest.split('\n');
|
||||
final updatedLines = updated.split('\n');
|
||||
// Should only add one line.
|
||||
expect(updatedLines.length, originalLines.length + 1);
|
||||
// First line is the manifest tag, unchanged.
|
||||
expect(updatedLines[0], originalLines[0]);
|
||||
// Second line is the new permission.
|
||||
expect(
|
||||
updatedLines[1],
|
||||
' <uses-permission '
|
||||
'android:name="android.permission.INTERNET"/>',
|
||||
);
|
||||
// Remaining lines are identical to the original.
|
||||
for (var i = 1; i < originalLines.length; i++) {
|
||||
expect(updatedLines[i + 1], originalLines[i]);
|
||||
}
|
||||
});
|
||||
|
||||
test('preserves existing formatting', () async {
|
||||
final manifestPath = p.join(
|
||||
projectRoot.path,
|
||||
'android',
|
||||
'app',
|
||||
'src',
|
||||
'main',
|
||||
);
|
||||
writeManifestToPath(manifestWithNoPermissions, manifestPath);
|
||||
|
||||
final results = await runWithOverrides(
|
||||
AndroidInternetPermissionValidator().validate,
|
||||
);
|
||||
await runWithOverrides(() => results.first.fix!());
|
||||
|
||||
final updated = File(
|
||||
p.join(manifestPath, 'AndroidManifest.xml'),
|
||||
).readAsStringSync();
|
||||
// The permission should be inserted after <manifest> with matching
|
||||
// indentation, and the rest of the file should be unchanged.
|
||||
expect(
|
||||
updated,
|
||||
contains(
|
||||
' <uses-permission '
|
||||
'android:name="android.permission.INTERNET"/>',
|
||||
),
|
||||
);
|
||||
// Original closing tag should remain untouched.
|
||||
expect(updated, contains('</manifest>'));
|
||||
// Should not collapse attributes onto one line (no reformatting).
|
||||
expect(
|
||||
updated,
|
||||
contains(
|
||||
' package="dev.shorebird.u_shorebird_clock">',
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user