mt-lang 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. checksums.yaml +7 -0
  2. data/.ruby-version +1 -0
  3. data/AUTHORS +3 -0
  4. data/Gemfile +19 -0
  5. data/Gemfile.lock +82 -0
  6. data/LICENSE +21 -0
  7. data/README.md +1208 -0
  8. data/Rakefile +332 -0
  9. data/bin/mtc +8 -0
  10. data/bin/profile-mtc-checks +133 -0
  11. data/bin/tracy-profiler +0 -0
  12. data/docs/build-guide.md +536 -0
  13. data/docs/index.html +2778 -0
  14. data/docs/language-design.md +1519 -0
  15. data/docs/language-manual.md +1534 -0
  16. data/lib/milk_tea/base.rb +49 -0
  17. data/lib/milk_tea/bindings/bindgen/ast_parser.rb +398 -0
  18. data/lib/milk_tea/bindings/bindgen/declaration.rb +319 -0
  19. data/lib/milk_tea/bindings/bindgen/emitter.rb +387 -0
  20. data/lib/milk_tea/bindings/bindgen/overrides.rb +134 -0
  21. data/lib/milk_tea/bindings/bindgen/type_mapper.rb +622 -0
  22. data/lib/milk_tea/bindings/bindgen.rb +207 -0
  23. data/lib/milk_tea/bindings/cli.rb +121 -0
  24. data/lib/milk_tea/bindings/imported_bindings/defaults.rb +210 -0
  25. data/lib/milk_tea/bindings/imported_bindings/generator.rb +1114 -0
  26. data/lib/milk_tea/bindings/imported_bindings/method_source.rb +190 -0
  27. data/lib/milk_tea/bindings/imported_bindings/naming.rb +286 -0
  28. data/lib/milk_tea/bindings/imported_bindings.rb +215 -0
  29. data/lib/milk_tea/bindings/opengl_registry.rb +547 -0
  30. data/lib/milk_tea/bindings/raw_bindings/defaults.rb +1338 -0
  31. data/lib/milk_tea/bindings/raw_bindings.rb +269 -0
  32. data/lib/milk_tea/bindings/steamworks.rb +629 -0
  33. data/lib/milk_tea/bindings/upstream_sources.rb +352 -0
  34. data/lib/milk_tea/bindings/vendored_box2d.rb +79 -0
  35. data/lib/milk_tea/bindings/vendored_c_library.rb +322 -0
  36. data/lib/milk_tea/bindings/vendored_cjson.rb +52 -0
  37. data/lib/milk_tea/bindings/vendored_flecs.rb +78 -0
  38. data/lib/milk_tea/bindings/vendored_glfw.rb +77 -0
  39. data/lib/milk_tea/bindings/vendored_libuv.rb +75 -0
  40. data/lib/milk_tea/bindings/vendored_pcre2.rb +84 -0
  41. data/lib/milk_tea/bindings/vendored_raylib.rb +131 -0
  42. data/lib/milk_tea/bindings/vendored_sdl3.rb +78 -0
  43. data/lib/milk_tea/bindings/vendored_steamworks.rb +71 -0
  44. data/lib/milk_tea/bindings/vendored_tool.rb +71 -0
  45. data/lib/milk_tea/bindings/vendored_tools.rb +23 -0
  46. data/lib/milk_tea/bindings/vendored_tracy.rb +37 -0
  47. data/lib/milk_tea/bindings.rb +23 -0
  48. data/lib/milk_tea/core/ast.rb +311 -0
  49. data/lib/milk_tea/core/async_runtime_installer.rb +28 -0
  50. data/lib/milk_tea/core/binding_types.rb +18 -0
  51. data/lib/milk_tea/core/bindings/attribute_binding.rb +58 -0
  52. data/lib/milk_tea/core/bindings/function_binding.rb +5 -0
  53. data/lib/milk_tea/core/bindings/module_binding.rb +58 -0
  54. data/lib/milk_tea/core/bindings/value_binding.rb +21 -0
  55. data/lib/milk_tea/core/c_backend/aggregate_utils.rb +103 -0
  56. data/lib/milk_tea/core/c_backend/control_flow_emission.rb +412 -0
  57. data/lib/milk_tea/core/c_backend/expressions.rb +468 -0
  58. data/lib/milk_tea/core/c_backend/feature_detection.rb +483 -0
  59. data/lib/milk_tea/core/c_backend/reachability.rb +398 -0
  60. data/lib/milk_tea/core/c_backend/reinterpret.rb +226 -0
  61. data/lib/milk_tea/core/c_backend/runtime_helpers.rb +1080 -0
  62. data/lib/milk_tea/core/c_backend/statements.rb +563 -0
  63. data/lib/milk_tea/core/c_backend/type_collectors.rb +1545 -0
  64. data/lib/milk_tea/core/c_backend/type_declaration.rb +223 -0
  65. data/lib/milk_tea/core/c_backend/type_system.rb +345 -0
  66. data/lib/milk_tea/core/c_backend.rb +287 -0
  67. data/lib/milk_tea/core/compatibility_helpers.rb +79 -0
  68. data/lib/milk_tea/core/compile_time/const_eval.rb +187 -0
  69. data/lib/milk_tea/core/compile_time.rb +329 -0
  70. data/lib/milk_tea/core/control_flow/builder.rb +582 -0
  71. data/lib/milk_tea/core/control_flow/constant_propagation.rb +143 -0
  72. data/lib/milk_tea/core/control_flow/dataflow.rb +74 -0
  73. data/lib/milk_tea/core/control_flow/definite_assignment.rb +79 -0
  74. data/lib/milk_tea/core/control_flow/graph.rb +90 -0
  75. data/lib/milk_tea/core/control_flow/liveness.rb +24 -0
  76. data/lib/milk_tea/core/control_flow/nullability_flow.rb +43 -0
  77. data/lib/milk_tea/core/control_flow/reachability.rb +22 -0
  78. data/lib/milk_tea/core/control_flow/termination.rb +31 -0
  79. data/lib/milk_tea/core/control_flow.rb +34 -0
  80. data/lib/milk_tea/core/cst.rb +48 -0
  81. data/lib/milk_tea/core/cst_builder.rb +19 -0
  82. data/lib/milk_tea/core/ir.rb +85 -0
  83. data/lib/milk_tea/core/keywords.rb +97 -0
  84. data/lib/milk_tea/core/lexer/character_classes.rb +60 -0
  85. data/lib/milk_tea/core/lexer/format_strings.rb +225 -0
  86. data/lib/milk_tea/core/lexer/heredocs.rb +199 -0
  87. data/lib/milk_tea/core/lexer/indentation.rb +62 -0
  88. data/lib/milk_tea/core/lexer/numbers.rb +96 -0
  89. data/lib/milk_tea/core/lexer/recovery.rb +32 -0
  90. data/lib/milk_tea/core/lexer/strings.rb +167 -0
  91. data/lib/milk_tea/core/lexer/symbols.rb +71 -0
  92. data/lib/milk_tea/core/lexer/trivia.rb +53 -0
  93. data/lib/milk_tea/core/lexer.rb +430 -0
  94. data/lib/milk_tea/core/lowering/artifacts.rb +26 -0
  95. data/lib/milk_tea/core/lowering/async/analysis.rb +245 -0
  96. data/lib/milk_tea/core/lowering/async/lowering.rb +1399 -0
  97. data/lib/milk_tea/core/lowering/async/normalization.rb +459 -0
  98. data/lib/milk_tea/core/lowering/async.rb +714 -0
  99. data/lib/milk_tea/core/lowering/block.rb +1052 -0
  100. data/lib/milk_tea/core/lowering/calls.rb +1565 -0
  101. data/lib/milk_tea/core/lowering/declarations.rb +214 -0
  102. data/lib/milk_tea/core/lowering/dyn.rb +206 -0
  103. data/lib/milk_tea/core/lowering/events.rb +1054 -0
  104. data/lib/milk_tea/core/lowering/expressions.rb +1645 -0
  105. data/lib/milk_tea/core/lowering/foreign_cstr.rb +206 -0
  106. data/lib/milk_tea/core/lowering/functions.rb +242 -0
  107. data/lib/milk_tea/core/lowering/loops.rb +1087 -0
  108. data/lib/milk_tea/core/lowering/lowering_context.rb +80 -0
  109. data/lib/milk_tea/core/lowering/proc.rb +419 -0
  110. data/lib/milk_tea/core/lowering/resolve.rb +2516 -0
  111. data/lib/milk_tea/core/lowering/scans.rb +220 -0
  112. data/lib/milk_tea/core/lowering/str_buffer.rb +125 -0
  113. data/lib/milk_tea/core/lowering/utils.rb +1453 -0
  114. data/lib/milk_tea/core/lowering.rb +378 -0
  115. data/lib/milk_tea/core/module_binder.rb +181 -0
  116. data/lib/milk_tea/core/module_loader/errors.rb +21 -0
  117. data/lib/milk_tea/core/module_loader.rb +479 -0
  118. data/lib/milk_tea/core/module_path_resolver.rb +153 -0
  119. data/lib/milk_tea/core/module_roots.rb +88 -0
  120. data/lib/milk_tea/core/parser/attributes.rb +71 -0
  121. data/lib/milk_tea/core/parser/blocks.rb +119 -0
  122. data/lib/milk_tea/core/parser/declarations.rb +749 -0
  123. data/lib/milk_tea/core/parser/expressions.rb +624 -0
  124. data/lib/milk_tea/core/parser/recovery.rb +131 -0
  125. data/lib/milk_tea/core/parser/statements.rb +756 -0
  126. data/lib/milk_tea/core/parser/types.rb +271 -0
  127. data/lib/milk_tea/core/parser.rb +400 -0
  128. data/lib/milk_tea/core/prelude_installer.rb +29 -0
  129. data/lib/milk_tea/core/pretty_printer/ast_formatter.rb +917 -0
  130. data/lib/milk_tea/core/pretty_printer/base_formatter.rb +87 -0
  131. data/lib/milk_tea/core/pretty_printer/ir_formatter.rb +300 -0
  132. data/lib/milk_tea/core/pretty_printer.rb +17 -0
  133. data/lib/milk_tea/core/semantic_analyzer/analysis_context.rb +314 -0
  134. data/lib/milk_tea/core/semantic_analyzer/attributes.rb +184 -0
  135. data/lib/milk_tea/core/semantic_analyzer/calls.rb +992 -0
  136. data/lib/milk_tea/core/semantic_analyzer/expressions.rb +1735 -0
  137. data/lib/milk_tea/core/semantic_analyzer/flow_refinement.rb +355 -0
  138. data/lib/milk_tea/core/semantic_analyzer/foreign_functions.rb +155 -0
  139. data/lib/milk_tea/core/semantic_analyzer/function_binding.rb +354 -0
  140. data/lib/milk_tea/core/semantic_analyzer/generics.rb +383 -0
  141. data/lib/milk_tea/core/semantic_analyzer/interface_conformance.rb +78 -0
  142. data/lib/milk_tea/core/semantic_analyzer/module_context.rb +35 -0
  143. data/lib/milk_tea/core/semantic_analyzer/name_resolution.rb +1438 -0
  144. data/lib/milk_tea/core/semantic_analyzer/nullability.rb +421 -0
  145. data/lib/milk_tea/core/semantic_analyzer/statements.rb +1308 -0
  146. data/lib/milk_tea/core/semantic_analyzer/top_level.rb +588 -0
  147. data/lib/milk_tea/core/semantic_analyzer/type_compatibility.rb +307 -0
  148. data/lib/milk_tea/core/semantic_analyzer/type_declaration.rb +851 -0
  149. data/lib/milk_tea/core/semantic_analyzer.rb +327 -0
  150. data/lib/milk_tea/core/token.rb +26 -0
  151. data/lib/milk_tea/core/token_stream.rb +30 -0
  152. data/lib/milk_tea/core/types/layout.rb +243 -0
  153. data/lib/milk_tea/core/types/predicates.rb +609 -0
  154. data/lib/milk_tea/core/types/registry.rb +83 -0
  155. data/lib/milk_tea/core/types/types.rb +1696 -0
  156. data/lib/milk_tea/core/types/visitor.rb +442 -0
  157. data/lib/milk_tea/core.rb +25 -0
  158. data/lib/milk_tea/dap/backends/lldb_dap.rb +158 -0
  159. data/lib/milk_tea/dap/protocol.rb +58 -0
  160. data/lib/milk_tea/dap/server/breakpoints.rb +66 -0
  161. data/lib/milk_tea/dap/server/debug_map.rb +291 -0
  162. data/lib/milk_tea/dap/server/handlers.rb +374 -0
  163. data/lib/milk_tea/dap/server/launch.rb +261 -0
  164. data/lib/milk_tea/dap/server/lldb_backend.rb +380 -0
  165. data/lib/milk_tea/dap/server/pause_diagnostics.rb +109 -0
  166. data/lib/milk_tea/dap/server/utilities.rb +160 -0
  167. data/lib/milk_tea/dap/server/wire.rb +55 -0
  168. data/lib/milk_tea/dap/server.rb +131 -0
  169. data/lib/milk_tea/dap/session.rb +152 -0
  170. data/lib/milk_tea/dap.rb +6 -0
  171. data/lib/milk_tea/lsp/dependency_resolution.rb +52 -0
  172. data/lib/milk_tea/lsp/diagnostics.rb +611 -0
  173. data/lib/milk_tea/lsp/protocol.rb +104 -0
  174. data/lib/milk_tea/lsp/server/call_hierarchy.rb +274 -0
  175. data/lib/milk_tea/lsp/server/code_actions.rb +446 -0
  176. data/lib/milk_tea/lsp/server/code_lens.rb +97 -0
  177. data/lib/milk_tea/lsp/server/completion.rb +1099 -0
  178. data/lib/milk_tea/lsp/server/configuration.rb +167 -0
  179. data/lib/milk_tea/lsp/server/debug_info.rb +45 -0
  180. data/lib/milk_tea/lsp/server/definition.rb +779 -0
  181. data/lib/milk_tea/lsp/server/diagnostics_scheduling.rb +239 -0
  182. data/lib/milk_tea/lsp/server/execute_command.rb +25 -0
  183. data/lib/milk_tea/lsp/server/folding_range.rb +153 -0
  184. data/lib/milk_tea/lsp/server/formatting.rb +575 -0
  185. data/lib/milk_tea/lsp/server/hover.rb +1465 -0
  186. data/lib/milk_tea/lsp/server/inlay_hints.rb +204 -0
  187. data/lib/milk_tea/lsp/server/lifecycle.rb +234 -0
  188. data/lib/milk_tea/lsp/server/linked_editing_range.rb +43 -0
  189. data/lib/milk_tea/lsp/server/on_type_formatting.rb +73 -0
  190. data/lib/milk_tea/lsp/server/progress.rb +47 -0
  191. data/lib/milk_tea/lsp/server/references.rb +433 -0
  192. data/lib/milk_tea/lsp/server/rename.rb +598 -0
  193. data/lib/milk_tea/lsp/server/selection_range.rb +130 -0
  194. data/lib/milk_tea/lsp/server/semantic_tokens.rb +1745 -0
  195. data/lib/milk_tea/lsp/server/signature_help.rb +200 -0
  196. data/lib/milk_tea/lsp/server/text_documents.rb +125 -0
  197. data/lib/milk_tea/lsp/server/type_hierarchy.rb +167 -0
  198. data/lib/milk_tea/lsp/server/utilities.rb +520 -0
  199. data/lib/milk_tea/lsp/server.rb +415 -0
  200. data/lib/milk_tea/lsp/workspace/analysis.rb +209 -0
  201. data/lib/milk_tea/lsp/workspace/caches.rb +260 -0
  202. data/lib/milk_tea/lsp/workspace/collection.rb +98 -0
  203. data/lib/milk_tea/lsp/workspace/definition_index.rb +184 -0
  204. data/lib/milk_tea/lsp/workspace/dependency_graph.rb +282 -0
  205. data/lib/milk_tea/lsp/workspace/store.rb +154 -0
  206. data/lib/milk_tea/lsp/workspace/utilities.rb +490 -0
  207. data/lib/milk_tea/lsp/workspace.rb +127 -0
  208. data/lib/milk_tea/lsp.rb +13 -0
  209. data/lib/milk_tea/packages/atomic_write.rb +63 -0
  210. data/lib/milk_tea/packages/dependency_solver.rb +194 -0
  211. data/lib/milk_tea/packages/graph.rb +139 -0
  212. data/lib/milk_tea/packages/lock.rb +421 -0
  213. data/lib/milk_tea/packages/manager_cli.rb +485 -0
  214. data/lib/milk_tea/packages/manifest.rb +356 -0
  215. data/lib/milk_tea/packages/manifest_editor.rb +134 -0
  216. data/lib/milk_tea/packages/registry_metadata_provider.rb +34 -0
  217. data/lib/milk_tea/packages/registry_store.rb +420 -0
  218. data/lib/milk_tea/packages/services.rb +41 -0
  219. data/lib/milk_tea/packages/source_cache.rb +71 -0
  220. data/lib/milk_tea/packages/source_fetcher.rb +124 -0
  221. data/lib/milk_tea/packages/source_resolver.rb +389 -0
  222. data/lib/milk_tea/packages/version.rb +164 -0
  223. data/lib/milk_tea/packages.rb +16 -0
  224. data/lib/milk_tea/tooling/asset_pack.rb +141 -0
  225. data/lib/milk_tea/tooling/build.rb +1124 -0
  226. data/lib/milk_tea/tooling/build_cache.rb +240 -0
  227. data/lib/milk_tea/tooling/cli.rb +2688 -0
  228. data/lib/milk_tea/tooling/cst_formatter.rb +13 -0
  229. data/lib/milk_tea/tooling/debug_info_formatter.rb +456 -0
  230. data/lib/milk_tea/tooling/debug_map.rb +248 -0
  231. data/lib/milk_tea/tooling/docs_app.rb +369 -0
  232. data/lib/milk_tea/tooling/error_formatter.rb +86 -0
  233. data/lib/milk_tea/tooling/formatter.rb +787 -0
  234. data/lib/milk_tea/tooling/linter/doc_tags.rb +212 -0
  235. data/lib/milk_tea/tooling/linter/fix_engine.rb +237 -0
  236. data/lib/milk_tea/tooling/linter/flow_rules.rb +380 -0
  237. data/lib/milk_tea/tooling/linter/imports_platform.rb +841 -0
  238. data/lib/milk_tea/tooling/linter/release_rules.rb +744 -0
  239. data/lib/milk_tea/tooling/linter/reserved_names.rb +199 -0
  240. data/lib/milk_tea/tooling/linter/rules.rb +593 -0
  241. data/lib/milk_tea/tooling/linter/source_helpers.rb +407 -0
  242. data/lib/milk_tea/tooling/linter/trailing_comma.rb +139 -0
  243. data/lib/milk_tea/tooling/linter/visitors.rb +1286 -0
  244. data/lib/milk_tea/tooling/linter.rb +798 -0
  245. data/lib/milk_tea/tooling/project_scaffold.rb +82 -0
  246. data/lib/milk_tea/tooling/public/css/docs.css +166 -0
  247. data/lib/milk_tea/tooling/public/js/docs.js +94 -0
  248. data/lib/milk_tea/tooling/run.rb +408 -0
  249. data/lib/milk_tea/tooling/templates/wasm_shell.html +48 -0
  250. data/lib/milk_tea/tooling/toolchain_cli.rb +157 -0
  251. data/lib/milk_tea/tooling/views/404.erb +7 -0
  252. data/lib/milk_tea/tooling/views/index.erb +87 -0
  253. data/lib/milk_tea/tooling/views/layout.erb +69 -0
  254. data/lib/milk_tea/tooling/views/module.erb +97 -0
  255. data/lib/milk_tea/tooling/views/stdlib.erb +21 -0
  256. data/lib/milk_tea/tooling.rb +20 -0
  257. data/lib/milk_tea.rb +8 -0
  258. metadata +426 -0
@@ -0,0 +1,194 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ class PackageDependencySolverError < StandardError; end
5
+
6
+ class PackageDependencySolver
7
+ Solution = Data.define(:registry_versions)
8
+
9
+ State = Data.define(:manifests_by_path, :selected_registry_versions, :processing_stack) do
10
+ def deep_dup
11
+ self.class.new(
12
+ manifests_by_path: manifests_by_path.dup,
13
+ selected_registry_versions: selected_registry_versions.dup,
14
+ processing_stack: processing_stack.dup,
15
+ )
16
+ end
17
+ end
18
+
19
+ def initialize(source_resolver:, registry_metadata_provider: PackageRegistryMetadataProvider.new, locked_registry_versions: nil)
20
+ @source_resolver = source_resolver
21
+ @registry_metadata_provider = registry_metadata_provider
22
+ @locked_registry_versions = normalize_locked_registry_versions(locked_registry_versions)
23
+ end
24
+
25
+ def solve(path_or_manifest)
26
+ root_manifest = path_or_manifest.is_a?(PackageManifest::DataView) ? path_or_manifest : PackageManifest.load(path_or_manifest)
27
+ state = State.new(
28
+ manifests_by_path: {},
29
+ selected_registry_versions: {},
30
+ processing_stack: [],
31
+ )
32
+
33
+ resolved_state = visit_manifest(root_manifest, state, source: @source_resolver.source_for_manifest(root_manifest))
34
+ Solution.new(registry_versions: resolved_state.selected_registry_versions.transform_values(&:to_s).freeze)
35
+ rescue PackageManifestError, PackageRegistryMetadataProviderError, PackageSourceResolverError, PackageVersionError => e
36
+ raise PackageDependencySolverError, e.message
37
+ end
38
+
39
+ private
40
+
41
+ def visit_manifest(manifest, state, source:)
42
+ existing_manifest = state.manifests_by_path[manifest.manifest_path]
43
+ if existing_manifest
44
+ return state
45
+ end
46
+
47
+ if state.processing_stack.include?(manifest.manifest_path)
48
+ cycle_paths = state.processing_stack + [manifest.manifest_path]
49
+ cycle = cycle_paths.map do |manifest_path|
50
+ if manifest_path == manifest.manifest_path
51
+ manifest.package_name
52
+ else
53
+ state.manifests_by_path.fetch(manifest_path).package_name
54
+ end
55
+ end
56
+ raise PackageDependencySolverError, "package dependency cycle detected: #{cycle.join(' -> ')}"
57
+ end
58
+
59
+ state.manifests_by_path[manifest.manifest_path] = manifest
60
+ state.processing_stack << manifest.manifest_path
61
+ process_dependency_list(manifest.dependencies, parent_manifest: manifest, parent_source: source, state:)
62
+ ensure
63
+ state.processing_stack.pop if state&.processing_stack&.last == manifest.manifest_path
64
+ end
65
+
66
+ def process_dependency_list(dependencies, parent_manifest:, parent_source:, state:, index: 0)
67
+ return state if index >= dependencies.length
68
+
69
+ candidate_states = visit_dependency(dependencies[index], parent_manifest:, parent_source:, state:)
70
+ last_error = nil
71
+ candidate_states.each do |candidate_state|
72
+ begin
73
+ return process_dependency_list(
74
+ dependencies,
75
+ parent_manifest:,
76
+ parent_source:,
77
+ state: candidate_state,
78
+ index: index + 1,
79
+ )
80
+ rescue PackageDependencySolverError => e
81
+ last_error = e
82
+ end
83
+ end
84
+
85
+ raise last_error || PackageDependencySolverError.new(
86
+ "failed to resolve dependency #{dependencies[index].name} in #{parent_manifest.manifest_path}",
87
+ )
88
+ end
89
+
90
+ def visit_dependency(dependency, parent_manifest:, parent_source:, state:)
91
+ if dependency.path || dependency.git
92
+ branch = state.deep_dup
93
+ resolved_package = @source_resolver.resolve(dependency, parent_manifest:, parent_source:)
94
+ return [visit_manifest(resolved_package.manifest, branch, source: resolved_package.source)]
95
+ end
96
+
97
+ if dependency.registry?
98
+ if dependency.exact_registry_version?
99
+ branch = state.deep_dup
100
+ resolved_package = @source_resolver.resolve(dependency, parent_manifest:, parent_source:)
101
+ return [visit_manifest(resolved_package.manifest, branch, source: resolved_package.source)]
102
+ end
103
+
104
+ return choose_registry_version(dependency, parent_manifest:, parent_source:, state:)
105
+ end
106
+
107
+ raise PackageDependencySolverError,
108
+ "dependency #{dependency.name} in #{parent_manifest.manifest_path} has no supported source resolver"
109
+ end
110
+
111
+ def choose_registry_version(dependency, parent_manifest:, parent_source:, state:)
112
+ package_name = dependency.name
113
+ dependency_key = registry_dependency_key(parent_source:, dependency_name: package_name)
114
+ candidate_versions = @registry_metadata_provider.available_versions(package_name).select do |candidate_version|
115
+ dependency.version_req.matches?(candidate_version)
116
+ end
117
+
118
+ locked_version = locked_registry_version_for(dependency_key, package_name)
119
+ if locked_version
120
+ candidate_versions = candidate_versions.select { |candidate_version| candidate_version.to_s == locked_version }
121
+ end
122
+
123
+ if candidate_versions.empty?
124
+ if locked_version
125
+ raise PackageDependencySolverError,
126
+ "locked registry version #{locked_version} for #{package_name} in #{parent_manifest.manifest_path} does not satisfy #{dependency.version_req}"
127
+ end
128
+
129
+ raise PackageDependencySolverError,
130
+ "no registry version for #{package_name} in #{parent_manifest.manifest_path} satisfies #{dependency.version_req}"
131
+ end
132
+
133
+ last_error = nil
134
+ candidate_states = candidate_versions.filter_map do |candidate_version|
135
+ branch = state.deep_dup
136
+ branch.selected_registry_versions[dependency_key] = candidate_version
137
+ manifest = @registry_metadata_provider.manifest_for(package_name, candidate_version)
138
+ source = PackageSourceResolver::Source.new(
139
+ identity: PackageSourceResolver::RegistryIdentity.new(package_name:, version: candidate_version.to_s),
140
+ local_root: manifest.root_dir,
141
+ )
142
+
143
+ begin
144
+ visit_manifest(manifest, branch, source:)
145
+ rescue PackageDependencySolverError => e
146
+ last_error = e
147
+ nil
148
+ end
149
+ end
150
+
151
+ return candidate_states unless candidate_states.empty?
152
+
153
+ raise last_error || PackageDependencySolverError.new(
154
+ "no registry version for #{package_name} in #{parent_manifest.manifest_path} satisfies #{dependency.version_req}",
155
+ )
156
+ end
157
+
158
+ def registry_dependency_key(parent_source:, dependency_name:)
159
+ PackageSourceResolver.registry_dependency_key(
160
+ parent_source_key: PackageSourceResolver.source_key_for_identity(parent_source.identity),
161
+ dependency_name:,
162
+ )
163
+ end
164
+
165
+ def locked_registry_version_for(dependency_key, package_name)
166
+ @locked_registry_versions[dependency_key] || @locked_registry_versions[package_name]
167
+ end
168
+
169
+ def normalize_locked_registry_versions(versions)
170
+ return {} unless versions
171
+
172
+ versions.each_with_object({}) do |(key, version), locked|
173
+ normalized_key = case key
174
+ when PackageSourceResolver::RegistryDependencyKey
175
+ PackageSourceResolver.registry_dependency_key(
176
+ parent_source_key: key.parent_source_key,
177
+ dependency_name: key.dependency_name,
178
+ )
179
+ when Array
180
+ if key.length != 2
181
+ raise PackageDependencySolverError,
182
+ "registry dependency key must contain parent manifest path and dependency name"
183
+ end
184
+
185
+ PackageSourceResolver.registry_dependency_key(parent_source_key: key[0], dependency_name: key[1])
186
+ else
187
+ key.to_s
188
+ end
189
+
190
+ locked[normalized_key] = version.to_s
191
+ end
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ class PackageGraphError < StandardError; end
5
+
6
+ class PackageGraph
7
+ Edge = Data.define(:dependency, :node)
8
+ Node = Data.define(:manifest, :source, :edges)
9
+
10
+ def self.load(path, locked: false, source_resolver: PackageSourceResolver.new, registry_metadata_provider: PackageRegistryMetadataProvider.new)
11
+ return PackageLock.load(path, source_resolver:) if locked
12
+
13
+ new(path, source_resolver:, registry_metadata_provider:).load
14
+ end
15
+
16
+ def initialize(path, source_resolver: PackageSourceResolver.new, registry_metadata_provider: PackageRegistryMetadataProvider.new)
17
+ @path = File.expand_path(path)
18
+ @source_resolver = source_resolver
19
+ @graph_source_resolver = source_resolver
20
+ @registry_metadata_provider = registry_metadata_provider
21
+ @nodes_by_manifest_path = {}
22
+ @build_stack = []
23
+ end
24
+
25
+ def load
26
+ root_manifest = PackageManifest.load(@path)
27
+ @graph_source_resolver = resolve_graph_source_resolver(root_manifest)
28
+ build_node(root_manifest, source: @graph_source_resolver.source_for_manifest(root_manifest))
29
+ end
30
+
31
+ def self.render_tree(path, source_resolver: PackageSourceResolver.new, registry_metadata_provider: PackageRegistryMetadataProvider.new)
32
+ load(path, source_resolver:, registry_metadata_provider:).tree_lines.join("\n")
33
+ end
34
+
35
+ class Node
36
+ def packages(nodes = [], visited = {})
37
+ return nodes if visited[manifest.manifest_path]
38
+
39
+ visited[manifest.manifest_path] = true
40
+ nodes << self
41
+ edges.each do |edge|
42
+ edge.node&.packages(nodes, visited)
43
+ end
44
+ nodes
45
+ end
46
+
47
+ def source_roots(roots = [], visited = {})
48
+ return roots if visited[manifest.manifest_path]
49
+
50
+ visited[manifest.manifest_path] = true
51
+ roots << manifest.source_root
52
+ edges.each do |edge|
53
+ edge.node&.source_roots(roots, visited)
54
+ end
55
+ roots
56
+ end
57
+
58
+ def package_for_path(path, matches = [])
59
+ return nil unless path
60
+
61
+ expanded_path = File.expand_path(path)
62
+ matches << self if package_path?(expanded_path)
63
+ edges.each do |edge|
64
+ edge.node&.package_for_path(expanded_path, matches)
65
+ end
66
+
67
+ matches.max_by { |node| node.manifest.root_dir.length }
68
+ end
69
+
70
+ def tree_lines(indent = 0)
71
+ lines = [(" " * indent) + manifest.package_name]
72
+ edges.each do |edge|
73
+ if edge.node
74
+ lines.concat(edge.node.tree_lines(indent + 1))
75
+ else
76
+ lines << ((" " * (indent + 1)) + edge.dependency.name)
77
+ end
78
+ end
79
+ lines
80
+ end
81
+
82
+ private
83
+
84
+ def package_path?(path)
85
+ root_dir = manifest.root_dir
86
+ path == root_dir || path.start_with?(root_dir + File::SEPARATOR)
87
+ end
88
+ end
89
+
90
+ private
91
+
92
+ def build_node(manifest, source:)
93
+ existing = @nodes_by_manifest_path[manifest.manifest_path]
94
+ return existing if existing
95
+
96
+ if @build_stack.include?(manifest.manifest_path)
97
+ cycle_paths = @build_stack + [manifest.manifest_path]
98
+ cycle_names = cycle_paths.map { |path| package_name_for_manifest_path(path) }
99
+ raise PackageGraphError, "package dependency cycle detected: #{cycle_names.join(' -> ')}"
100
+ end
101
+
102
+ @build_stack << manifest.manifest_path
103
+ edges = manifest.dependencies.map do |dependency|
104
+ resolved_package = @graph_source_resolver.resolve(dependency, parent_manifest: manifest, parent_source: source)
105
+ edge_node = build_node(resolved_package.manifest, source: resolved_package.source)
106
+ Edge.new(dependency:, node: edge_node)
107
+ end
108
+
109
+ node = Node.new(manifest:, source:, edges:)
110
+ @nodes_by_manifest_path[manifest.manifest_path] = node
111
+ node
112
+ ensure
113
+ @build_stack.pop if @build_stack.last == manifest.manifest_path
114
+ end
115
+
116
+ def package_name_for_manifest_path(manifest_path)
117
+ node = @nodes_by_manifest_path[manifest_path]
118
+ return node.manifest.package_name if node
119
+
120
+ PackageManifest.load(manifest_path).package_name
121
+ rescue PackageManifestError
122
+ manifest_path
123
+ end
124
+
125
+ def resolve_graph_source_resolver(root_manifest)
126
+ return @source_resolver unless @source_resolver.supports_dependency_solving?
127
+
128
+ solution = PackageDependencySolver.new(
129
+ source_resolver: @source_resolver,
130
+ registry_metadata_provider: @registry_metadata_provider,
131
+ locked_registry_versions: @source_resolver.resolved_registry_versions,
132
+ ).solve(root_manifest)
133
+
134
+ @source_resolver.with_resolved_registry_versions(solution.registry_versions)
135
+ rescue PackageDependencySolverError => e
136
+ raise PackageGraphError, e.message
137
+ end
138
+ end
139
+ end