From 09d02deaba8ab57e2f489d43319cba3ddf88b52d Mon Sep 17 00:00:00 2001 From: Seungpyo1007 Date: Fri, 7 Aug 2026 16:27:10 +0900 Subject: [PATCH] chore: add IDE Dart library generator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Android Studio에서 lib/main.dart를 비롯한 모든 파일의 import가 빨간 줄로 뜨는 문제가 있었다. 원인은 .idea/libraries/ 디렉터리가 통째로 사라진 것. .idea/TechPicks.iml은 Dart SDK, Dart Packages, Flutter Plugins 세 라이브러리를 참조하는데 정의 파일이 없으니 전부 미해결로 남았다. 원래 Dart·Flutter 플러그인이 자동 생성하는 파일이지만, 손상되거나 브랜치를 옮겨 다니는 사이 사라지면 IDE가 스스로 복구하지 못하는 경우가 있다. 이 스크립트가 세 파일을 다시 만든다. - Dart_SDK.xml: flutter 실행 파일 위치에서 번들 SDK 경로를 역산해 등록 - Dart_Packages.xml: .dart_tool/package_config.json 이 유일한 근거 - Flutter_Plugins.xml: .flutter-plugins-dependencies 의 네이티브 플러그인 package: 임포트 없이 dart:io 와 dart:convert 만 쓰므로 패키지 해석이 깨진 상태에서도 `dart tool/gen_idea_libs.dart` 로 실행된다. 정작 고쳐야 할 상황이 그 상황이라서 그렇게 만들었다. .idea/ 자체는 gitignore 대상이라 스크립트만 커밋된다. --- tool/gen_idea_libs.dart | 194 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 tool/gen_idea_libs.dart diff --git a/tool/gen_idea_libs.dart b/tool/gen_idea_libs.dart new file mode 100644 index 0000000..6a39a85 --- /dev/null +++ b/tool/gen_idea_libs.dart @@ -0,0 +1,194 @@ +// IntelliJ / Android Studio가 Dart를 인식하도록 .idea/libraries/*.xml 을 만든다. +// +// 이 파일들은 원래 Dart·Flutter 플러그인이 자동 생성하지만, .idea 가 손상되거나 +// 브랜치를 옮겨 다니는 사이 사라지면 모든 import 가 빨간 줄로 뜬다. 그때 쓴다. +// +// dart tool/gen_idea_libs.dart +// +// 실행 전에 `flutter pub get` 으로 .dart_tool/package_config.json 이 현재 +// 브랜치의 pubspec 과 맞는 상태여야 한다. 이게 어긋난 채로 돌리면 IDE 가 +// 존재하지 않는 버전을 가리킨다. +// +// 끝나면 IDE 를 완전히 닫았다 다시 열 것. 열려 있으면 종료 시 덮어쓴다. +import 'dart:convert'; +import 'dart:io'; + +void main(List args) { + final root = Directory.current; + final libsDir = Directory('${root.path}/.idea/libraries'); + libsDir.createSync(recursive: true); + + final sdk = _dartSdkPath(); + if (sdk == null) { + stderr.writeln('Dart SDK를 찾지 못했다. flutter가 PATH에 있는지 확인할 것.'); + exit(1); + } + + _writeDartSdk(libsDir, sdk); + final count = _writeDartPackages(libsDir, root); + final plugins = _writeFlutterPlugins(libsDir, root); + + stdout + ..writeln('Dart SDK $sdk') + ..writeln('Dart Packages $count개') + ..writeln('Flutter Plugins $plugins개') + ..writeln('') + ..writeln('IDE를 완전히 닫았다가 다시 열 것.'); +} + +/// `flutter` 실행 파일 위치에서 번들 Dart SDK 경로를 역산한다. +String? _dartSdkPath() { + final which = Process.runSync('which', ['flutter']); + if (which.exitCode != 0) return null; + final binDir = File((which.stdout as String).trim()).resolveSymbolicLinksSync(); + // .../flutter/bin/flutter -> .../flutter/bin/cache/dart-sdk + final flutterBin = Directory(binDir).parent.path; + final sdk = '$flutterBin/cache/dart-sdk'; + return Directory(sdk).existsSync() ? sdk : null; +} + +/// dart:async 같은 SDK 라이브러리를 등록한다. +void _writeDartSdk(Directory libsDir, String sdk) { + final libRoot = Directory('$sdk/lib'); + final roots = libRoot + .listSync() + .whereType() + .map((d) => d.path.split('/').last) + // 앞에 밑줄이 붙은 것과 컴파일러 내부 디렉터리는 공개 라이브러리가 아니다. + .where((n) => !n.startsWith('_') && n != 'dev_compiler' && n != 'internal') + .toList() + ..sort(); + + final entries = + roots.map((n) => ' ').join('\n'); + + File('${libsDir.path}/Dart_SDK.xml').writeAsStringSync(''' + + + +$entries + + + + + +'''); +} + +/// pub 이 해석한 패키지들을 등록한다. package_config.json 이 유일한 근거다. +int _writeDartPackages(Directory libsDir, Directory root) { + final configFile = File('${root.path}/.dart_tool/package_config.json'); + if (!configFile.existsSync()) { + stderr.writeln('.dart_tool/package_config.json 이 없다. flutter pub get 먼저.'); + exit(1); + } + final config = jsonDecode(configFile.readAsStringSync()) as Map; + final packages = (config['packages'] as List).cast>(); + + final entries = StringBuffer(); + final classes = StringBuffer(); + var count = 0; + + for (final pkg in packages) { + final name = pkg['name'] as String; + final rootUri = pkg['rootUri'] as String; + final packageUri = (pkg['packageUri'] as String?) ?? 'lib/'; + + // 자기 자신은 소스 폴더라 라이브러리로 넣지 않는다. + if (name == _projectName(root)) continue; + + final base = rootUri.startsWith('file://') + ? Uri.parse(rootUri).toFilePath() + : File('${root.path}/.dart_tool/$rootUri').absolute.path; + final libPath = _normalize('$base/$packageUri'); + + if (!Directory(libPath).existsSync()) continue; + + entries.writeln(''' + + + + + '''); + classes.writeln(' '); + count++; + } + + File('${libsDir.path}/Dart_Packages.xml').writeAsStringSync(''' + + + + + + +${classes.toString().trimRight()} + + + + + +'''); + return count; +} + +/// 네이티브 코드를 가진 플러그인만 따로 등록한다. Flutter 플러그인이 쓰는 목록이다. +int _writeFlutterPlugins(Directory libsDir, Directory root) { + final file = File('${root.path}/.flutter-plugins-dependencies'); + if (!file.existsSync()) return 0; + + final data = jsonDecode(file.readAsStringSync()) as Map; + final plugins = data['plugins'] as Map?; + if (plugins == null) return 0; + + final paths = {}; + for (final platform in plugins.values) { + if (platform is! List) continue; + for (final entry in platform.cast>()) { + final path = entry['path'] as String?; + if (path != null) paths.add(path.replaceAll(RegExp(r'/$'), '')); + } + } + + final roots = (paths.toList()..sort()) + .map((p) => ' ') + .join('\n'); + + File('${libsDir.path}/Flutter_Plugins.xml').writeAsStringSync(''' + + + +$roots + + + + + +'''); + return paths.length; +} + +String _projectName(Directory root) { + final pubspec = File('${root.path}/pubspec.yaml'); + if (!pubspec.existsSync()) return ''; + for (final line in pubspec.readAsLinesSync()) { + if (line.startsWith('name:')) return line.substring(5).trim(); + } + return ''; +} + +/// `a/b/../c` 같은 경로를 정리한다. +String _normalize(String path) { + final parts = []; + for (final seg in path.split('/')) { + if (seg == '.' || seg.isEmpty) continue; + if (seg == '..') { + if (parts.isNotEmpty) parts.removeLast(); + } else { + parts.add(seg); + } + } + return '/${parts.join('/')}'; +}