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,485 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ module MilkTea
6
+ class PackageManagerCLI
7
+ def self.start(argv = ARGV, out:, err:, help_printer:, services: PackageServices.new)
8
+ new(argv, out:, err:, help_printer:, services:).start
9
+ end
10
+
11
+ def initialize(argv, out:, err:, help_printer:, services:)
12
+ @argv = argv.dup
13
+ @out = out
14
+ @err = err
15
+ @help_printer = help_printer
16
+ @services = services
17
+ end
18
+
19
+ def start
20
+ subcommand = @argv.shift
21
+ unless subcommand
22
+ @err.puts("missing deps subcommand")
23
+ print_help
24
+ return 1
25
+ end
26
+
27
+ case subcommand
28
+ when "add"
29
+ deps_add_command
30
+ when "remove"
31
+ deps_remove_command
32
+ when "update"
33
+ deps_update_command
34
+ when "tree"
35
+ path = nil
36
+ if @argv.first && !@argv.first.start_with?("-")
37
+ path = @argv.shift
38
+ end
39
+
40
+ unless path
41
+ if File.file?(File.join(Dir.pwd, "package.toml"))
42
+ path = Dir.pwd
43
+ else
44
+ @err.puts("missing package path")
45
+ print_help
46
+ return 1
47
+ end
48
+ end
49
+
50
+ if @argv.any?
51
+ @err.puts("unknown deps option #{@argv.first}")
52
+ print_help
53
+ return 1
54
+ end
55
+
56
+ @out.puts(PackageGraph.render_tree(path, source_resolver: @services.source_resolver(:materialize)))
57
+ 0
58
+ when "lock"
59
+ path = nil
60
+ check = false
61
+ until @argv.empty?
62
+ arg = @argv.shift
63
+ case arg
64
+ when "--check"
65
+ check = true
66
+ when /^-/
67
+ @err.puts("unknown deps option #{arg}")
68
+ print_help
69
+ return 1
70
+ else
71
+ if path
72
+ @err.puts("unknown deps option #{arg}")
73
+ print_help
74
+ return 1
75
+ end
76
+
77
+ path = arg
78
+ end
79
+ end
80
+
81
+ unless path
82
+ if File.file?(File.join(Dir.pwd, "package.toml"))
83
+ path = Dir.pwd
84
+ else
85
+ @err.puts("missing package path")
86
+ print_help
87
+ return 1
88
+ end
89
+ end
90
+
91
+ if check
92
+ result = PackageLock.check(path, source_resolver: @services.source_resolver(:materialize))
93
+ if result.current?
94
+ @out.puts("up to date #{result.lock_path}")
95
+ 0
96
+ elsif result.missing?
97
+ @out.puts("missing #{result.lock_path}")
98
+ 1
99
+ else
100
+ @out.puts("out of date #{result.lock_path}")
101
+ 1
102
+ end
103
+ else
104
+ result = PackageLock.write(path, source_resolver: @services.source_resolver(:materialize))
105
+ @out.puts("wrote #{result.lock_path}")
106
+ 0
107
+ end
108
+ when "publish"
109
+ path = nil
110
+ upstream = false
111
+ until @argv.empty?
112
+ arg = @argv.shift
113
+ case arg
114
+ when "--upstream"
115
+ upstream = true
116
+ when /^-/
117
+ @err.puts("unknown deps option #{arg}")
118
+ print_help
119
+ return 1
120
+ else
121
+ if path
122
+ @err.puts("unknown deps option #{arg}")
123
+ print_help
124
+ return 1
125
+ end
126
+
127
+ path = arg
128
+ end
129
+ end
130
+
131
+ unless path
132
+ if File.file?(File.join(Dir.pwd, "package.toml"))
133
+ path = Dir.pwd
134
+ else
135
+ @err.puts("missing package path")
136
+ print_help
137
+ return 1
138
+ end
139
+ end
140
+
141
+ result = @services.registry_store.publish(path, target: upstream ? :upstream : :local)
142
+ @out.puts("published #{result.package_name}@#{result.version} -> #{result.path}")
143
+ 0
144
+ when "fetch"
145
+ path = nil
146
+ until @argv.empty?
147
+ arg = @argv.shift
148
+ case arg
149
+ when /^-/
150
+ @err.puts("unknown deps option #{arg}")
151
+ print_help
152
+ return 1
153
+ else
154
+ if path
155
+ @err.puts("unknown deps option #{arg}")
156
+ print_help
157
+ return 1
158
+ end
159
+
160
+ path = arg
161
+ end
162
+ end
163
+
164
+ unless path
165
+ if File.file?(File.join(Dir.pwd, "package.toml"))
166
+ path = Dir.pwd
167
+ else
168
+ @err.puts("missing package path")
169
+ print_help
170
+ return 1
171
+ end
172
+ end
173
+
174
+ emit_dependency_fetch_results(@services.source_fetcher.fetch_locked_sources(path), path)
175
+ 0
176
+ else
177
+ @err.puts("unknown deps subcommand #{subcommand}")
178
+ print_help
179
+ 1
180
+ end
181
+ end
182
+
183
+ private
184
+
185
+ def print_help
186
+ @help_printer.call(@err)
187
+ end
188
+
189
+ def deps_add_command
190
+ path = deps_target_path_from_argv!
191
+ dependency_arg = @argv.shift
192
+ unless dependency_arg
193
+ @err.puts("missing dependency name")
194
+ print_help
195
+ return 1
196
+ end
197
+
198
+ dependency_name, inline_requirement = parse_dependency_argument(dependency_arg)
199
+ spec = parse_dependency_spec_for_add(dependency_name, inline_requirement)
200
+ editor = PackageManifestEditor.new(path)
201
+ existing = PackageManifest.load(path).dependencies.any? { |dependency| dependency.name == dependency_name }
202
+
203
+ with_manifest_edit(editor) do
204
+ editor.add_dependency(dependency_name, spec)
205
+ end
206
+
207
+ action = existing ? "updated" : "added"
208
+ @out.puts("#{action} #{dependency_name} in #{editor.manifest_path}")
209
+ 0
210
+ end
211
+
212
+ def deps_remove_command
213
+ path = deps_target_path_from_argv!
214
+ dependency_name = @argv.shift
215
+ unless dependency_name
216
+ @err.puts("missing dependency name")
217
+ print_help
218
+ return 1
219
+ end
220
+
221
+ if @argv.any?
222
+ @err.puts("unknown deps option #{@argv.first}")
223
+ print_help
224
+ return 1
225
+ end
226
+
227
+ editor = PackageManifestEditor.new(path)
228
+ with_manifest_edit(editor) do
229
+ editor.remove_dependency(dependency_name)
230
+ end
231
+
232
+ @out.puts("removed #{dependency_name} from #{editor.manifest_path}")
233
+ 0
234
+ end
235
+
236
+ def deps_update_command
237
+ path = deps_target_path_from_argv!
238
+ dependency_names = parse_update_dependency_names
239
+ return 1 if dependency_names.nil?
240
+
241
+ lock_result = PackageLock.write(path, source_resolver: selective_update_source_resolver(path, dependency_names))
242
+ @out.puts("updated #{lock_result.lock_path}")
243
+ emit_dependency_fetch_results(@services.source_fetcher.fetch_locked_sources(path), path)
244
+ 0
245
+ end
246
+
247
+ def deps_target_path_from_argv!
248
+ path = nil
249
+ if @argv.first && !@argv.first.start_with?("-") && package_path_argument?(@argv.first)
250
+ path = @argv.shift
251
+ end
252
+
253
+ return path if path
254
+ return Dir.pwd if File.file?(File.join(Dir.pwd, "package.toml"))
255
+
256
+ raise PackageManifestEditorError, "missing package path"
257
+ end
258
+
259
+ def package_path_argument?(value)
260
+ File.exist?(value) || value.end_with?("package.toml") || value.include?(File::SEPARATOR)
261
+ end
262
+
263
+ def parse_dependency_argument(argument)
264
+ name, requirement = argument.split("@", 2)
265
+ if name.nil? || name.strip.empty?
266
+ raise PackageManifestEditorError, "dependency name cannot be empty"
267
+ end
268
+
269
+ if requirement && requirement.strip.empty?
270
+ raise PackageManifestEditorError, "dependency version requirement cannot be empty"
271
+ end
272
+
273
+ [name.strip, requirement&.strip]
274
+ end
275
+
276
+ def parse_update_dependency_names
277
+ names = []
278
+ until @argv.empty?
279
+ arg = @argv.shift
280
+ if arg.start_with?("-")
281
+ @err.puts("unknown deps option #{arg}")
282
+ print_help
283
+ return nil
284
+ end
285
+
286
+ names << arg
287
+ end
288
+
289
+ names
290
+ end
291
+
292
+ def selective_update_source_resolver(path, dependency_names)
293
+ source_resolver = @services.source_resolver(:materialize)
294
+ return source_resolver if dependency_names.empty?
295
+
296
+ manifest = PackageManifest.load(path)
297
+ locked_packages = PackageLock.locked_packages(path)
298
+ ensure_selective_update_lock_current!(path, manifest, locked_packages, source_resolver)
299
+ known_package_names = manifest.dependencies.each_with_object(Set.new) do |dependency, names|
300
+ names << dependency.name
301
+ end
302
+ locked_packages.each { |package| known_package_names << package.package_name }
303
+
304
+ unknown_names = dependency_names.reject { |name| known_package_names.include?(name) }
305
+ unless unknown_names.empty?
306
+ raise PackageManifestEditorError,
307
+ "cannot selectively update unknown package #{unknown_names.first} in #{manifest.manifest_path}"
308
+ end
309
+
310
+ unlocked_package_ids = selective_update_package_instance_ids(locked_packages, dependency_names)
311
+ pinned_versions = locked_registry_dependency_versions(
312
+ locked_packages,
313
+ unlocked_instance_ids: unlocked_package_ids,
314
+ )
315
+
316
+ source_resolver.with_resolved_registry_versions(pinned_versions)
317
+ rescue PackageLockError => e
318
+ raise PackageManifestEditorError,
319
+ "selective deps update requires a current package.lock; run `mtc deps lock #{manifest.root_dir}` or `mtc deps update #{manifest.root_dir}` first (#{e.message})"
320
+ end
321
+
322
+ def ensure_selective_update_lock_current!(path, manifest, locked_packages, source_resolver)
323
+ pinned_versions = locked_registry_dependency_versions(locked_packages)
324
+
325
+ lock_check = PackageLock.check(
326
+ path,
327
+ source_resolver: source_resolver.with_resolved_registry_versions(pinned_versions),
328
+ )
329
+ return if lock_check.current?
330
+
331
+ raise PackageManifestEditorError,
332
+ "selective deps update requires a current package.lock that matches the current manifest; run `mtc deps lock #{manifest.root_dir}` or `mtc deps update #{manifest.root_dir}` first"
333
+ rescue PackageGraphError => e
334
+ raise PackageManifestEditorError,
335
+ "selective deps update requires a current package.lock that matches the current manifest; run `mtc deps lock #{manifest.root_dir}` or `mtc deps update #{manifest.root_dir}` first (#{e.message})"
336
+ end
337
+
338
+ def selective_update_package_instance_ids(locked_packages, requested_names)
339
+ packages_by_id = locked_packages.each_with_object({}) do |package, packages|
340
+ packages[package.instance_id] = package
341
+ end
342
+ selected_ids = Set.new
343
+ queue = locked_packages.select { |package| requested_names.include?(package.package_name) }
344
+ .map(&:instance_id)
345
+
346
+ until queue.empty?
347
+ package_id = queue.shift
348
+ next if selected_ids.include?(package_id)
349
+
350
+ selected_ids << package_id
351
+ package = packages_by_id[package_id]
352
+ next unless package
353
+
354
+ package.dependency_ids.each do |dependency_id|
355
+ queue << dependency_id
356
+ end
357
+ end
358
+
359
+ selected_ids
360
+ end
361
+
362
+ def locked_registry_dependency_versions(locked_packages, unlocked_instance_ids: Set.new)
363
+ packages_by_id = locked_packages.each_with_object({}) do |package, packages|
364
+ packages[package.instance_id] = package
365
+ end
366
+
367
+ locked_packages.each_with_object({}) do |package, versions|
368
+ parent_source_key = PackageSourceResolver.source_key_for_identity(package.identity)
369
+
370
+ package.dependency_ids.each do |dependency_id|
371
+ dependency = packages_by_id[dependency_id]
372
+ next unless dependency
373
+ next unless dependency.identity.is_a?(PackageSourceResolver::RegistryIdentity)
374
+ next if unlocked_instance_ids.include?(dependency.instance_id)
375
+
376
+ key = PackageSourceResolver.registry_dependency_key(
377
+ parent_source_key:,
378
+ dependency_name: dependency.package_name,
379
+ )
380
+ versions[key] = dependency.identity.version
381
+ end
382
+ end
383
+ end
384
+
385
+ def parse_dependency_spec_for_add(dependency_name, inline_requirement)
386
+ requirement = inline_requirement
387
+ path = nil
388
+ git = nil
389
+ git_rev = nil
390
+ git_subdir = nil
391
+
392
+ until @argv.empty?
393
+ arg = @argv.shift
394
+ case arg
395
+ when "--path"
396
+ path = deps_required_option_value!(arg)
397
+ when "--git"
398
+ git = deps_required_option_value!(arg)
399
+ when "--rev"
400
+ git_rev = deps_required_option_value!(arg)
401
+ when "--subdir"
402
+ git_subdir = deps_required_option_value!(arg)
403
+ when "--version"
404
+ requirement = deps_required_option_value!(arg)
405
+ else
406
+ @err.puts("unknown deps option #{arg}")
407
+ print_help
408
+ raise PackageManifestEditorError, "unknown deps option #{arg}"
409
+ end
410
+ end
411
+
412
+ if path && git
413
+ raise PackageManifestEditorError, "dependency #{dependency_name} cannot use both --path and --git"
414
+ end
415
+
416
+ if git
417
+ raise PackageManifestEditorError, "dependency #{dependency_name} is missing --rev" unless git_rev
418
+ raise PackageManifestEditorError, "dependency #{dependency_name} cannot combine git resolution with a version requirement" if requirement
419
+
420
+ spec = { "git" => git, "rev" => git_rev }
421
+ spec["subdir"] = git_subdir if git_subdir
422
+ return spec
423
+ end
424
+
425
+ if path
426
+ spec = { "path" => path }
427
+ spec["version"] = normalize_dependency_requirement_string(requirement, dependency_name) if requirement
428
+ return spec
429
+ end
430
+
431
+ raise PackageManifestEditorError, "dependency #{dependency_name} is missing a version requirement" unless requirement
432
+
433
+ normalize_dependency_requirement_string(requirement, dependency_name)
434
+ end
435
+
436
+ def deps_required_option_value!(option)
437
+ value = @argv.shift
438
+ raise PackageManifestEditorError, "missing value for #{option}" unless value
439
+
440
+ value
441
+ end
442
+
443
+ def normalize_dependency_requirement_string(requirement, dependency_name)
444
+ normalized_text = requirement.to_s.strip
445
+ version_req = PackageVersionReq.parse(
446
+ normalized_text,
447
+ label: "dependency #{dependency_name} version requirement",
448
+ )
449
+ version_req.exact? ? version_req.exact_version.to_s : normalized_text
450
+ end
451
+
452
+ def with_manifest_edit(editor)
453
+ original_source = File.read(editor.manifest_path)
454
+ lock_path = File.join(File.dirname(editor.manifest_path), "package.lock")
455
+ original_lock_content = File.file?(lock_path) ? File.binread(lock_path) : nil
456
+
457
+ yield
458
+ lock_result = PackageLock.write(editor.manifest_path, source_resolver: @services.source_resolver(:materialize))
459
+ fetch_results = @services.source_fetcher.fetch_locked_sources(editor.manifest_path)
460
+ @out.puts("wrote #{lock_result.lock_path}")
461
+ emit_dependency_fetch_results(fetch_results, editor.manifest_path)
462
+ rescue StandardError
463
+ PackageAtomicWrite.write(editor.manifest_path, original_source) if original_source
464
+ if original_lock_content
465
+ PackageAtomicWrite.write(lock_path, original_lock_content, binmode: true)
466
+ elsif File.file?(lock_path)
467
+ File.delete(lock_path)
468
+ end
469
+ raise
470
+ end
471
+
472
+ def emit_dependency_fetch_results(results, path)
473
+ if results.empty?
474
+ lock_path = File.join(PackageManifest.load(path).root_dir, "package.lock")
475
+ @out.puts("no cache-backed sources in #{lock_path}")
476
+ return
477
+ end
478
+
479
+ results.each do |result|
480
+ verb = result.status == :present ? "kept" : "materialized"
481
+ @out.puts("#{verb} #{result.package_name} -> #{result.path}")
482
+ end
483
+ end
484
+ end
485
+ end