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,421 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+ require "tomlrb"
5
+
6
+ module MilkTea
7
+ class PackageLockError < StandardError; end
8
+
9
+ class PackageLock
10
+ Result = Data.define(:lock_path, :content)
11
+ LockedPackage = Data.define(:package_name, :instance_id, :identity, :manifest_path, :dependency_names, :dependency_ids)
12
+ CheckResult = Data.define(:lock_path, :expected_content, :actual_content, :status) do
13
+ def current?
14
+ status == :current
15
+ end
16
+
17
+ def missing?
18
+ status == :missing
19
+ end
20
+
21
+ def stale?
22
+ status == :stale
23
+ end
24
+ end
25
+
26
+ def self.load(path, source_resolver: PackageSourceResolver.new)
27
+ new(path, source_resolver:).load
28
+ end
29
+
30
+ def self.check(path, source_resolver: PackageSourceResolver.new)
31
+ new(path, source_resolver:).check
32
+ end
33
+
34
+ def self.locked_packages(path, source_resolver: PackageSourceResolver.new)
35
+ new(path, source_resolver:).locked_packages
36
+ end
37
+
38
+ def self.write(path, source_resolver: PackageSourceResolver.new)
39
+ new(path, source_resolver:).write
40
+ end
41
+
42
+ def initialize(path, source_resolver: PackageSourceResolver.new)
43
+ @path = File.expand_path(path)
44
+ @source_resolver = source_resolver
45
+ end
46
+
47
+ def load
48
+ _root_manifest, _lock_path, _root_package_name, root_package_id, records_by_id = locked_records
49
+
50
+ raise PackageLockError, "package.lock missing root package #{root_package_id}" unless records_by_id.key?(root_package_id)
51
+
52
+ build_locked_node(root_package_id, records_by_id, {}, [])
53
+ end
54
+
55
+ def locked_packages
56
+ _root_manifest, lock_path, _root_package_name, root_package_id, schema_version, packages = lockfile_context
57
+
58
+ records_by_id = packages.each_with_object({}) do |entry, records|
59
+ record = build_locked_package_record(entry, lock_path, schema_version)
60
+ if records.key?(record[:instance_id])
61
+ raise PackageLockError, "duplicate package instance #{record[:instance_id]} in #{lock_path}"
62
+ end
63
+
64
+ records[record[:instance_id]] = record
65
+ end
66
+
67
+ raise PackageLockError, "package.lock missing root package #{root_package_id}" unless records_by_id.key?(root_package_id)
68
+
69
+ records_by_id.values.map do |record|
70
+ dependency_names = record[:dependency_ids].map do |dependency_id|
71
+ dependency_record = records_by_id[dependency_id]
72
+ unless dependency_record
73
+ raise PackageLockError,
74
+ "package.lock missing dependency #{dependency_id} referenced by #{record[:package_name]}"
75
+ end
76
+
77
+ dependency_record[:package_name]
78
+ end
79
+
80
+ LockedPackage.new(
81
+ package_name: record[:package_name],
82
+ instance_id: record[:instance_id],
83
+ identity: record[:identity],
84
+ manifest_path: record[:manifest_path],
85
+ dependency_names:,
86
+ dependency_ids: record[:dependency_ids],
87
+ )
88
+ end
89
+ end
90
+
91
+ def check
92
+ lock_path, expected_content = rendered_lockfile
93
+ return CheckResult.new(lock_path:, expected_content:, actual_content: nil, status: :missing) unless File.file?(lock_path)
94
+
95
+ actual_content = File.read(lock_path)
96
+ status = actual_content == expected_content ? :current : :stale
97
+ CheckResult.new(lock_path:, expected_content:, actual_content:, status:)
98
+ rescue SystemCallError => e
99
+ raise PackageLockError, "failed to read #{lock_path || File.join(@path, 'package.lock')}: #{e.message}"
100
+ end
101
+
102
+ def write
103
+ lock_path, content = rendered_lockfile
104
+
105
+ PackageAtomicWrite.write(lock_path, content)
106
+
107
+ Result.new(lock_path:, content:)
108
+ rescue SystemCallError => e
109
+ raise PackageLockError, "failed to write #{lock_path || File.join(@path, 'package.lock')}: #{e.message}"
110
+ end
111
+
112
+ private
113
+
114
+ def lockfile_context
115
+ root_manifest = PackageManifest.load(@path)
116
+ lock_path = File.join(root_manifest.root_dir, "package.lock")
117
+ raise PackageLockError, "package lock not found: #{lock_path}" unless File.file?(lock_path)
118
+
119
+ config = parse_lockfile(lock_path)
120
+ schema_version = config["schema_version"]
121
+ unless [1, 2].include?(schema_version)
122
+ raise PackageLockError, "unsupported package.lock schema version #{schema_version.inspect}; expected 1 or 2"
123
+ end
124
+
125
+ root_package_name = config["root_package"].to_s
126
+ if root_package_name.empty?
127
+ raise PackageLockError, "package.lock missing root_package: #{lock_path}"
128
+ end
129
+
130
+ if root_package_name != root_manifest.package_name
131
+ raise PackageLockError, "package.lock root package #{root_package_name} does not match package manifest #{root_manifest.package_name}"
132
+ end
133
+
134
+ packages = config["package"]
135
+ unless packages.is_a?(Array) && !packages.empty?
136
+ raise PackageLockError, "package.lock missing [[package]] entries: #{lock_path}"
137
+ end
138
+
139
+ root_package_id = if schema_version == 1
140
+ root_package_name
141
+ else
142
+ required_string(config, "root_package_id", lock_path)
143
+ end
144
+
145
+ [root_manifest, lock_path, root_package_name, root_package_id, schema_version, packages]
146
+ end
147
+
148
+ def locked_records
149
+ _root_manifest, lock_path, root_package_name, root_package_id, schema_version, packages = lockfile_context
150
+
151
+ records_by_id = packages.each_with_object({}) do |entry, records|
152
+ record = build_locked_record(entry, lock_path, schema_version)
153
+ if records.key?(record[:instance_id])
154
+ raise PackageLockError, "duplicate package instance #{record[:instance_id]} in #{lock_path}"
155
+ end
156
+
157
+ records[record[:instance_id]] = record
158
+ end
159
+
160
+ [PackageManifest.load(@path), lock_path, root_package_name, root_package_id, records_by_id]
161
+ end
162
+
163
+ def rendered_lockfile
164
+ root = PackageGraph.load(@path, source_resolver: @source_resolver)
165
+ lock_path = File.join(root.manifest.root_dir, "package.lock")
166
+ [lock_path, render(root)]
167
+ end
168
+
169
+ def parse_lockfile(path)
170
+ data = Tomlrb.load_file(path)
171
+ raise PackageLockError, "package.lock root must be a table: #{path}" unless data.is_a?(Hash)
172
+
173
+ data
174
+ rescue StandardError => e
175
+ raise PackageLockError, "invalid package.lock at #{path}: #{e.message}"
176
+ end
177
+
178
+ def build_locked_record(entry, lock_path, schema_version)
179
+ unless entry.is_a?(Hash)
180
+ raise PackageLockError, "invalid [[package]] entry in #{lock_path}"
181
+ end
182
+
183
+ package_name = required_string(entry, "name", lock_path)
184
+ instance_id = schema_version == 1 ? package_name : required_string(entry, "instance_id", lock_path)
185
+ source = @source_resolver.source_from_lock(entry, lock_path)
186
+ manifest = PackageManifest.load(source.local_root)
187
+ if manifest.package_name != package_name
188
+ raise PackageLockError,
189
+ "package.lock entry #{package_name} resolved materialized package #{manifest.package_name} at #{manifest.manifest_path}"
190
+ end
191
+
192
+ if source.kind == :registry
193
+ expected_version = source.identity.version
194
+ if manifest.package_version != expected_version
195
+ raise PackageLockError,
196
+ "package.lock registry entry #{package_name} resolved version #{manifest.package_version.inspect} at #{manifest.manifest_path}; expected #{expected_version.inspect}"
197
+ end
198
+ end
199
+
200
+ dependency_ids = normalize_dependency_ids(
201
+ schema_version == 1 ? entry["dependencies"] : entry["dependency_ids"],
202
+ lock_path,
203
+ )
204
+
205
+ manifest = PackageManifest::DataView.new(
206
+ root_dir: manifest.root_dir,
207
+ manifest_path: manifest.manifest_path,
208
+ package_name: manifest.package_name,
209
+ package_version: manifest.package_version,
210
+ package_kind: manifest.package_kind,
211
+ source_root: manifest.source_root,
212
+ source_path: nil,
213
+ profile: nil,
214
+ platform: nil,
215
+ output_path: nil,
216
+ assets_paths: [],
217
+ html_template_path: nil,
218
+ dependencies: [],
219
+ )
220
+
221
+ {
222
+ instance_id:,
223
+ package_name:,
224
+ manifest:,
225
+ source:,
226
+ dependency_ids:,
227
+ }
228
+ rescue PackageManifestError, PackageSourceResolverError => e
229
+ raise PackageLockError, e.message
230
+ end
231
+
232
+ def build_locked_package(entry, lock_path)
233
+ build_locked_package_record(entry, lock_path, 1)
234
+ end
235
+
236
+ def build_locked_package_record(entry, lock_path, schema_version)
237
+ unless entry.is_a?(Hash)
238
+ raise PackageLockError, "invalid [[package]] entry in #{lock_path}"
239
+ end
240
+
241
+ package_name = required_string(entry, "name", lock_path)
242
+ instance_id = schema_version == 1 ? package_name : required_string(entry, "instance_id", lock_path)
243
+ identity = @source_resolver.identity_from_lock(entry, lock_path)
244
+ manifest_path = File.expand_path(required_string(entry, "manifest_path", lock_path))
245
+ dependency_ids = normalize_dependency_ids(
246
+ schema_version == 1 ? entry["dependencies"] : entry["dependency_ids"],
247
+ lock_path,
248
+ )
249
+
250
+ {
251
+ package_name:,
252
+ instance_id:,
253
+ identity:,
254
+ manifest_path:,
255
+ dependency_ids:,
256
+ }
257
+ rescue PackageSourceResolverError => e
258
+ raise PackageLockError, e.message
259
+ end
260
+
261
+ def build_locked_node(instance_id, records_by_id, nodes_by_id, build_stack)
262
+ existing = nodes_by_id[instance_id]
263
+ return existing if existing
264
+
265
+ if build_stack.include?(instance_id)
266
+ cycle = build_stack + [instance_id]
267
+ raise PackageLockError, "package dependency cycle detected in package.lock: #{cycle.join(' -> ')}"
268
+ end
269
+
270
+ record = records_by_id[instance_id]
271
+ raise PackageLockError, "package.lock missing package #{instance_id}" unless record
272
+
273
+ build_stack << instance_id
274
+ dependencies = record[:dependency_ids].map do |dependency_id|
275
+ dependency_record = records_by_id[dependency_id]
276
+ unless dependency_record
277
+ raise PackageLockError,
278
+ "package.lock missing dependency #{dependency_id} referenced by #{record[:package_name]}"
279
+ end
280
+
281
+ dependency_record
282
+ end
283
+
284
+ manifest = record[:manifest]
285
+ manifest = PackageManifest::DataView.new(
286
+ root_dir: manifest.root_dir,
287
+ manifest_path: manifest.manifest_path,
288
+ package_name: manifest.package_name,
289
+ package_version: manifest.package_version,
290
+ package_kind: manifest.package_kind,
291
+ source_root: manifest.source_root,
292
+ source_path: manifest.source_path,
293
+ profile: manifest.profile,
294
+ platform: manifest.platform,
295
+ output_path: manifest.output_path,
296
+ assets_paths: manifest.assets_paths,
297
+ html_template_path: manifest.html_template_path,
298
+ dependencies: dependencies.map do |dependency_record|
299
+ PackageManifest::DependencyView.new(dependency_record[:package_name], nil, nil, nil, nil, nil, nil)
300
+ end,
301
+ )
302
+
303
+ edges = dependencies.map do |dependency_record|
304
+ dependency = PackageManifest::DependencyView.new(dependency_record[:package_name], nil, nil, nil, nil, nil, nil)
305
+ node = build_locked_node(dependency_record[:instance_id], records_by_id, nodes_by_id, build_stack)
306
+ PackageGraph::Edge.new(dependency:, node:)
307
+ end
308
+
309
+ node = PackageGraph::Node.new(manifest:, source: record[:source], edges:)
310
+ nodes_by_id[instance_id] = node
311
+ node
312
+ ensure
313
+ build_stack.pop if build_stack.last == instance_id
314
+ end
315
+
316
+ def required_string(entry, key, lock_path)
317
+ value = entry[key]
318
+ text = value.to_s
319
+ raise PackageLockError, "package.lock missing #{key} in #{lock_path}" if text.empty?
320
+
321
+ text
322
+ end
323
+
324
+ def optional_string(value)
325
+ return nil if value.nil?
326
+
327
+ text = value.to_s
328
+ text.empty? ? nil : text
329
+ end
330
+
331
+ def normalize_dependency_ids(value, lock_path)
332
+ case value
333
+ when nil
334
+ []
335
+ when Array
336
+ value.map do |item|
337
+ item_text = item.to_s
338
+ raise PackageLockError, "package.lock dependency ids must be strings in #{lock_path}" if item_text.empty?
339
+
340
+ item_text
341
+ end
342
+ else
343
+ raise PackageLockError, "package.lock dependencies must be an array in #{lock_path}"
344
+ end
345
+ end
346
+
347
+ def normalize_package_kind(value, lock_path)
348
+ case value
349
+ when "application", "app", "executable"
350
+ :application
351
+ when "library", "lib"
352
+ :library
353
+ else
354
+ raise PackageLockError, "unknown package kind #{value.inspect} in #{lock_path}"
355
+ end
356
+ end
357
+
358
+ def render(root)
359
+ root_package_id = package_instance_id(root.manifest.package_name, root.source)
360
+ lines = [
361
+ "schema_version = 2",
362
+ "root_package = #{toml_string(root.manifest.package_name)}",
363
+ "root_package_id = #{toml_string(root_package_id)}",
364
+ "",
365
+ ]
366
+
367
+ packages = root.packages.sort_by do |node|
368
+ [node.manifest.package_name, package_instance_id(node.manifest.package_name, node.source)]
369
+ end
370
+ packages.each_with_index do |node, index|
371
+ manifest = node.manifest
372
+ source = node.source
373
+ instance_id = package_instance_id(manifest.package_name, source)
374
+ dependency_edges = node.edges.select(&:node).sort_by do |edge|
375
+ [edge.dependency.name, package_instance_id(edge.node.manifest.package_name, edge.node.source)]
376
+ end
377
+ lines << "[[package]]"
378
+ lines << "name = #{toml_string(manifest.package_name)}"
379
+ lines << "kind = #{toml_string(manifest.package_kind.to_s)}"
380
+ lines << "version = #{toml_string(manifest.package_version)}" if manifest.package_version
381
+ lines << "instance_id = #{toml_string(instance_id)}"
382
+ lines << "source_kind = #{toml_string(source.kind.to_s)}"
383
+ source.lock_attributes.sort.each do |key, value|
384
+ lines << "#{key} = #{toml_string(value)}"
385
+ end
386
+ lines << "manifest_path = #{toml_string(manifest.manifest_path)}"
387
+ lines << "source_root = #{toml_string(manifest.source_root)}"
388
+ lines << "dependencies = #{toml_array(dependency_edges.map { |edge| edge.dependency.name })}"
389
+ lines << "dependency_ids = #{toml_array(dependency_edges.map { |edge| package_instance_id(edge.node.manifest.package_name, edge.node.source) })}"
390
+ lines << "" unless index == packages.length - 1
391
+ end
392
+
393
+ lines.join("\n") + "\n"
394
+ end
395
+
396
+ def package_instance_id(package_name, source)
397
+ digest_parts = [package_name.to_s, source.kind.to_s]
398
+ source.lock_attributes.sort.each do |key, value|
399
+ digest_parts << key.to_s
400
+ digest_parts << value.to_s
401
+ end
402
+
403
+ Digest::SHA256.hexdigest(digest_parts.join("\0"))
404
+ end
405
+
406
+ def toml_array(values)
407
+ "[#{values.map { |value| toml_string(value) }.join(', ')}]"
408
+ end
409
+
410
+ def toml_string(value)
411
+ escaped = value.to_s
412
+ .gsub('\\', '\\\\')
413
+ .gsub('"', '\\"')
414
+ .gsub("\n", '\\n')
415
+ .gsub("\r", '\\r')
416
+ .gsub("\t", '\\t')
417
+
418
+ %Q("#{escaped}")
419
+ end
420
+ end
421
+ end