环境预备
安装 graphviz
- arch -arm64 brew install graphviz
复制代码 项目根目录pubspec.yaml文件内添加
- dev_dependencies:
- yaml: ^3.1.1
- gviz: ^0.4.0
复制代码 执行脚本
项目根目录下添加dart文件,运行main函数
- import 'dart:io';
- import 'dart:convert';
- import 'package:yaml/yaml.dart' as yaml;
- import 'package:gviz/gviz.dart';
- void main() async {
- final projectPath = await _getProjectPath();
- final file = File('$projectPath/pubspec.yaml');
- final fileContent = file.readAsStringSync();
- final yamlMap = yaml.loadYaml(fileContent) as yaml.YamlMap;
- final appName = yamlMap['name'].toString();
- print('开始 ...');
- final dependencyContent = await _getComponentDependencyTree(
- projectPath: projectPath,
- );
- print('... 开始遍历组件依赖节点');
- print(dependencyContent);
- final dependencyNodes = _traversalComponentDependencyTree(dependencyContent);
- print('... 完成遍历组件依赖节点');
- final graph = Gviz(
- name: appName,
- graphProperties: {
- 'pad': '0.5',
- 'nodesep': '1',
- 'ranksep': '2',
- },
- edgeProperties: {
- 'fontcolor': 'gray',
- },
- );
- print('... 开始转换 dot 节点');
- _generateDotByNodes(
- dependencyNodes,
- graph: graph,
- edgeCache: <String>[],
- );
- print('... 完成转换 dot 节点');
- final dotDirectoryPath = '$projectPath/dotGenerateDir';
- final dotDirectory = Directory(dotDirectoryPath);
- if (!dotDirectory.existsSync()) {
- await dotDirectory.create();
- print('... 创建 dotGenerate 文件夹');
- }
- final dotFileName = '$appName.dot';
- final dotPngName = '$appName.png';
- final dotFile = File('$dotDirectoryPath/$dotFileName');
- final dotPngFile = File('$dotDirectoryPath/$dotPngName');
- if (dotFile.existsSync()) {
- await dotFile.delete();
- print('... 删除原有 dot 生成文件');
- }
- if (dotPngFile.existsSync()) {
- await dotPngFile.delete();
- print('... 删除原有 dot 依赖关系图');
- }
- await dotFile.create();
- final dotResult = await dotFile.writeAsString(graph.toString());
- print('dot 文件生成成功: ${dotResult.path}');
- print('... 开始生成 dot png');
- await _runCommand(
- executable: 'dot',
- projectPath: projectPath,
- commandArgs: [
- '$dotDirectoryPath/$dotFileName',
- '-T',
- 'png',
- '-o',
- '$dotDirectoryPath/$dotPngName'
- ],
- );
- print('png 文件生成成功:$dotDirectoryPath/$dotPngName');
- await Process.run(
- 'open',
- [dotDirectoryPath],
- );
- }
- const List<String> ignoreDependency = <String>[
- 'flutter',
- 'flutter_test',
- 'flutter_lints',
- 'cupertino_icons',
- 'gviz',
- 'yaml',
- 'injectable_generator',
- 'build_runner',
- ];
- Future<String> _getComponentDependencyTree({
- required String projectPath,
- }) {
- return _runCommand(
- projectPath: projectPath,
- commandArgs: ['pub', 'deps', '--json'],
- ).then(
- (value) {
- if (value.contains('dependencies:') &&
- value.contains('dev dependencies:')) {
- final start = value.indexOf('dependencies:');
- final end = value.indexOf('dev dependencies:');
- return value.substring(start, end);
- } else {
- return value;
- }
- },
- );
- }
- List<DependencyNode> _traversalComponentDependencyTree(String dependencyContent) {
- final dependencyJson = jsonDecode(dependencyContent) as Map<String, dynamic>;
- final packages = dependencyJson['packages'] as List<dynamic>;
- final nodeMap = <String, DependencyNode>{};
- for (var package in packages) {
- final node = DependencyNode.fromMap(package);
- nodeMap[node.name] = node;
- }
- final rootNode = nodeMap.values.firstWhere((element) => element.isRootNode);
- void mapDependencies(DependencyNode node, Set<String> visitedNodes) {
- if (visitedNodes.contains(node.name)) {
- return;
- }
- visitedNodes.add(node.name);
- for (final itemName in node.dependencies) {
- if (!ignoreDependency.contains(itemName)) {
- final itemNode = nodeMap[itemName];
- if (itemNode != null) {
- mapDependencies(itemNode, visitedNodes);
- node.children.add(itemNode);
- itemNode.isLevel1Node = false;
- }
- }
- }
- }
- final visitedNodes = <String>{};
- mapDependencies(rootNode, visitedNodes);
- // 使用新的 rebuildDependencyTree 函数来创建一个没有重复依赖的新树
- DependencyNode newRootNode = rebuildDependencyTree(rootNode, Set<String>());
- return [newRootNode];
- }
- DependencyNode rebuildDependencyTree(DependencyNode originalNode, Set<String> seenDependencies) {
- // 创建一个新的节点,复制原始节点的属性
- DependencyNode newNode = DependencyNode(
- name: originalNode.name,
- version: originalNode.version,
- kind: originalNode.kind,
- source: originalNode.source,
- dependencies: originalNode.dependencies,
- );
- newNode.isLevel1Node = originalNode.isLevel1Node;
- // 如果这个节点已经被处理过,直接返回新节点(没有子节点)
- if (seenDependencies.contains(newNode.name)) {
- return newNode;
- }
- // 将这个节点添加到已处理集合中
- seenDependencies.add(newNode.name);
- // 处理子节点
- for (var childNode in originalNode.children) {
- if (!ignoreDependency.contains(childNode.name)) {
- var newChildNode = rebuildDependencyTree(childNode, seenDependencies);
- newNode.children.add(newChildNode);
- }
- }
- return newNode;
- }
- Future<String> _getProjectPath() async {
- final originProjectPath = await Process.run(
- 'pwd',
- [],
- );
- final projectPath = (originProjectPath.stdout as String).replaceAll(
- '\n',
- '',
- );
- return projectPath;
- }
- void _generateDotByNodes(
- List<DependencyNode> nodes, {
- required Gviz graph,
- required List<String> edgeCache,
- }) {
- if (nodes.isEmpty) {
- return;
- }
- for (int index = 0; index < nodes.length; index++) {
- final itemNode = nodes[index];
- final from = '${itemNode.name}\n${itemNode.version}';
- if (!graph.nodeExists(from)) {
- graph.addNode(
- from,
- properties: {
- 'color': 'black',
- 'shape': 'rectangle',
- 'margin': '1,0.8',
- 'penwidth': '7',
- 'style': 'filled',
- 'fillcolor': 'gray',
- 'fontsize': itemNode.isLevel1Node ? '60' : '55',
- },
- );
- }
- final toArr = itemNode.children.map((e) => '${e.name}\n${e.version}').toList();
- for (var element in toArr) {
- final edgeKey = '$from-$element';
- if (!edgeCache.contains(edgeKey)) {
- graph.addEdge(
- from,
- element,
- properties: {
- 'penwidth': '2',
- 'style': 'dashed',
- 'arrowed': 'vee',
- },
- );
- edgeCache.add(edgeKey);
- }
- }
- _generateDotByNodes(
- itemNode.children,
- graph: graph,
- edgeCache: edgeCache,
- );
- }
- }
- Future<String> _runCommand({
- String executable = 'flutter',
- required String projectPath,
- required List<String> commandArgs,
- }) {
- return Process.run(
- executable,
- commandArgs,
- runInShell: true,
- workingDirectory: projectPath,
- ).then((result) => result.stdout as String);
- }
- class DependencyNode {
- final String name;
- final String version;
- final String kind;
- final String source;
- final List<String> dependencies;
- final children = <DependencyNode>[];
- bool isLevel1Node = true;
- factory DependencyNode.fromMap(Map<String, dynamic> map) {
- return DependencyNode(
- name: map['name'] as String,
- version: map['version'] as String,
- kind: map['kind'] as String,
- source: map['source'] as String,
- dependencies: (map['dependencies'] as List<dynamic>)
- .map((e) => e as String)
- .toList(),
- );
- }
- bool get isRootNode => kind == 'root';
- @override
- bool operator ==(Object other) =>
- identical(this, other) ||
- other is DependencyNode &&
- runtimeType == other.runtimeType &&
- name == other.name;
- @override
- int get hashCode => name.hashCode;
- DependencyNode({
- required this.name,
- required this.version,
- required this.kind,
- required this.source,
- required this.dependencies,
- });
- }
复制代码 输出示例
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |