tiny.smg.scan.reconcile.projection
Defined in scan.reconcile.
API (5)
Actions
Public operations.
Types and contracts
Public types and contracts.
Source
Source: tools/smg/src/scan/reconcile/projection.zig
zig
const std = @import("std");const sys = @import("sys");const smg = @import("../../root.zig");const reconcile = @import("root.zig");const graph_mod = smg.graph;const model = smg.model;const scan_root = smg.scan;const core = scan_root.core;pub const Plan = struct { files: []const []const u8, displaced_edges: []const model.Edge,};pub fn clonePlan(allocator: std.mem.Allocator, plan: Plan) !Plan { const files = try allocator.alloc([]const u8, plan.files.len); var file_count: usize = 0; errdefer { for (files[0..file_count]) |file| allocator.free(file); allocator.free(files); } for (plan.files, 0..) |file, index| { files[index] = try allocator.dupe(u8, file); file_count += 1; } const edges = try allocator.alloc(model.Edge, plan.displaced_edges.len); var edge_count: usize = 0; errdefer { for (edges[0..edge_count]) |*edge| model.deinitEdge(edge, allocator); allocator.free(edges); } for (plan.displaced_edges, 0..) |edge, index| { edges[index] = try model.cloneEdge(allocator, edge); edge_count += 1; } return .{ .files = files, .displaced_edges = edges };}pub fn prepare( allocator: std.mem.Allocator, graph: *graph_mod.Graph, root: []const u8, paths: []const []const u8, replace: bool, options: core.Options, stats: *core.Stats,) !Plan { const selected = try reconcile.scope.collect(allocator, graph.*, root, paths, options); return .{ .files = selected.files, .displaced_edges = try retire(allocator, graph, root, selected, replace, stats), };}pub fn finish(allocator: std.mem.Allocator, graph: *graph_mod.Graph, plan: Plan, stats: *core.Stats) !void { for (plan.displaced_edges) |edge| { const source_exists = graph_mod.getNode(graph, edge.source) != null; const target_exists = graph_mod.getNode(graph, edge.target) != null; if (!source_exists and target_exists and hierarchyEdge(edge)) try ensurePackageHierarchy(allocator, graph, edge.target, stats); } for (plan.displaced_edges) |edge| { if (graph_mod.getNode(graph, edge.source) != null and graph_mod.getNode(graph, edge.target) != null) { _ = try graph_mod.addEdgeTracked(graph, edge); continue; } if (!manualEdge(edge)) continue; stats.edges_removed += 1; try stats.orphaned_manual_edges.append(allocator, .{ .source = edge.source, .rel = edge.rel, .target = edge.target, .reason = if (graph_mod.getNode(graph, edge.source) == null) "source node removed" else "target node removed", }); }}fn retire( allocator: std.mem.Allocator, graph: *graph_mod.Graph, root: []const u8, selected: reconcile.scope.Scope, replace: bool, stats: *core.Stats,) ![]const model.Edge { var live = std.StringHashMap(void).init(allocator); defer live.deinit(); for (selected.files) |file| try live.put(file, {}); var exact = std.StringHashMap(void).init(allocator); defer exact.deinit(); for (selected.exact) |file| try exact.put(file, {}); var existence = std.StringHashMap(bool).init(allocator); defer existence.deinit(); var removed = std.StringHashMap(void).init(allocator); defer removed.deinit(); var packages = std.StringHashMap(void).init(allocator); defer packages.deinit(); for (graph.nodes.items) |node| { if (!graph_mod.isScan(node.metadata)) continue; const file = node.file orelse continue; if (!try shouldRetireFile(allocator, root, selected, replace, file, live, exact, &existence)) continue; try removed.put(node.name, {}); try addSyntheticPackageAncestors(allocator, graph.*, node.name, &packages); } try addEmptySyntheticPackages(graph.*, &removed, packages); if (removed.count() == 0) return &.{}; var displaced_edges: std.ArrayList(model.Edge) = .empty; for (graph.edges.items) |edge| { if (!removed.contains(edge.source) and !removed.contains(edge.target)) continue; if (manualEdge(edge) or !removed.contains(edge.source) or (!removed.contains(edge.target) and hierarchyEdge(edge))) try displaced_edges.append(allocator, edge); } var names: std.ArrayList([]const u8) = .empty; for (graph.nodes.items) |node| if (removed.contains(node.name)) try names.append(allocator, node.name); try graph_mod.removeNodes(graph, names.items); stats.nodes_removed += names.items.len; return try displaced_edges.toOwnedSlice(allocator);}fn shouldRetireFile( allocator: std.mem.Allocator, root: []const u8, selected: reconcile.scope.Scope, replace: bool, file: []const u8, live: std.StringHashMap(void), exact: std.StringHashMap(void), existence: *std.StringHashMap(bool),) !bool { const selected_tree = scan_root.scan_files.underPrefixes(selected.trees, file); if (replace and (live.contains(file) or selected_tree)) return true; if (exact.contains(file)) return true; if (!selected_tree) return false; const entry = try existence.getOrPut(file); if (!entry.found_existing) { entry.value_ptr.* = exists: { const stat = sys.fs.statFile(try std.fs.path.join(allocator, &.{ root, file })) catch |err| switch (err) { error.FileNotFound, error.NotDir => break :exists false, else => return err, }; break :exists stat.kind == .file; }; } return !entry.value_ptr.*;}fn manualEdge(edge: model.Edge) bool { return std.mem.eql(u8, model.pairValue(edge.metadata, "source") orelse "", "manual");}fn hierarchyEdge(edge: model.Edge) bool { return std.mem.eql(u8, edge.rel, model.RelType.contains) and graph_mod.isScan(edge.metadata);}pub fn ensurePackageHierarchy(allocator: std.mem.Allocator, graph: *graph_mod.Graph, module: []const u8, stats: *core.Stats) !void { var parts: std.ArrayList([]const u8) = .empty; var split = std.mem.splitScalar(u8, module, '.'); while (split.next()) |part| try parts.append(allocator, part); if (parts.items.len < 2) return; var parent: ?[]const u8 = null; var index: usize = 0; while (index + 1 < parts.items.len) : (index += 1) { const current = try core.joinDotted(allocator, parts.items[0 .. index + 1]); if (graph_mod.getNode(graph, current) == null) { const meta = try model.sourcePair(allocator, "scan"); try graph_mod.addNode(graph, .{ .name = current, .type = model.NodeType.package, .metadata = meta }); } if (parent) |parent_name| { const meta = try model.sourcePair(allocator, "scan"); try core.addScanEdge(graph, stats, .{ .source = parent_name, .rel = model.RelType.contains, .target = current, .metadata = meta }); } parent = current; } if (parent) |parent_name| { const meta = try model.sourcePair(allocator, "scan"); try core.addScanEdge(graph, stats, .{ .source = parent_name, .rel = model.RelType.contains, .target = module, .metadata = meta }); }}fn addSyntheticPackageAncestors(allocator: std.mem.Allocator, graph: graph_mod.Graph, name: []const u8, packages: *std.StringHashMap(void)) !void { for (graph_mod.incomingEdges(&graph, name)) |edge_index| { const edge = graph.edges.items[edge_index]; if (!std.mem.eql(u8, edge.rel, model.RelType.contains)) continue; const parent = graph_mod.getNode(&graph, edge.source) orelse continue; if (!std.mem.eql(u8, parent.type, model.NodeType.package) or parent.file != null or !graph_mod.isScan(parent.metadata)) continue; const entry = try packages.getOrPut(parent.name); if (entry.found_existing) continue; try addSyntheticPackageAncestors(allocator, graph, parent.name, packages); }}fn addEmptySyntheticPackages(graph: graph_mod.Graph, removed: *std.StringHashMap(void), packages: std.StringHashMap(void)) !void { var remaining = std.StringHashMap(usize).init(graph.allocator); defer remaining.deinit(); var queue: std.ArrayList([]const u8) = .empty; defer queue.deinit(graph.allocator); var iterator = packages.iterator(); while (iterator.next()) |entry| { const name = entry.key_ptr.*; var count: usize = 0; for (graph_mod.outgoingEdges(&graph, name)) |edge_index| { const edge = graph.edges.items[edge_index]; if (std.mem.eql(u8, edge.rel, model.RelType.contains) and !removed.contains(edge.target)) count += 1; } try remaining.put(name, count); if (count == 0) try queue.append(graph.allocator, name); } var index: usize = 0; while (index < queue.items.len) : (index += 1) { const name = queue.items[index]; if (removed.contains(name)) continue; try removed.put(name, {}); for (graph_mod.incomingEdges(&graph, name)) |edge_index| { const edge = graph.edges.items[edge_index]; if (!std.mem.eql(u8, edge.rel, model.RelType.contains)) continue; const count = remaining.getPtr(edge.source) orelse continue; if (count.* == 0) continue; count.* -= 1; if (count.* == 0) try queue.append(graph.allocator, edge.source); } }}fn checkClonePlanAllocationFailures(allocator: std.mem.Allocator) !void { const metadata = [_]model.Pair{.{ .key = "source", .value = "manual" }}; const edges = [_]model.Edge{.{ .source = "manual", .rel = model.RelType.depends_on, .target = "app.main", .metadata = &metadata, }}; const cloned = try clonePlan(allocator, .{ .files = &.{ "app.py", "lib.py" }, .displaced_edges = &edges, }); defer { for (cloned.files) |file| allocator.free(file); allocator.free(cloned.files); for (@constCast(cloned.displaced_edges)) |*edge| model.deinitEdge(edge, allocator); allocator.free(cloned.displaced_edges); } try std.testing.expectEqualStrings("app.py", cloned.files[0]); try std.testing.expectEqualStrings("manual", cloned.displaced_edges[0].source);}test "reconciliation plan clone cleans every allocation failure" { try std.testing.checkAllAllocationFailures( std.testing.allocator, checkClonePlanAllocationFailures, .{}, );}Source: tools/smg/src/scan/reconcile/root.zig:2
zig
pub const projection = @import("projection.zig");Audit
| Definitions | 6 |
|---|---|
| Public names | 11 |
| Members | 2 |
| Version | 26.7.0 |
| Revision | daab053ee433 |