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.
- checksums.yaml +7 -0
- data/.ruby-version +1 -0
- data/AUTHORS +3 -0
- data/Gemfile +19 -0
- data/Gemfile.lock +82 -0
- data/LICENSE +21 -0
- data/README.md +1208 -0
- data/Rakefile +332 -0
- data/bin/mtc +8 -0
- data/bin/profile-mtc-checks +133 -0
- data/bin/tracy-profiler +0 -0
- data/docs/build-guide.md +536 -0
- data/docs/index.html +2778 -0
- data/docs/language-design.md +1519 -0
- data/docs/language-manual.md +1534 -0
- data/lib/milk_tea/base.rb +49 -0
- data/lib/milk_tea/bindings/bindgen/ast_parser.rb +398 -0
- data/lib/milk_tea/bindings/bindgen/declaration.rb +319 -0
- data/lib/milk_tea/bindings/bindgen/emitter.rb +387 -0
- data/lib/milk_tea/bindings/bindgen/overrides.rb +134 -0
- data/lib/milk_tea/bindings/bindgen/type_mapper.rb +622 -0
- data/lib/milk_tea/bindings/bindgen.rb +207 -0
- data/lib/milk_tea/bindings/cli.rb +121 -0
- data/lib/milk_tea/bindings/imported_bindings/defaults.rb +210 -0
- data/lib/milk_tea/bindings/imported_bindings/generator.rb +1114 -0
- data/lib/milk_tea/bindings/imported_bindings/method_source.rb +190 -0
- data/lib/milk_tea/bindings/imported_bindings/naming.rb +286 -0
- data/lib/milk_tea/bindings/imported_bindings.rb +215 -0
- data/lib/milk_tea/bindings/opengl_registry.rb +547 -0
- data/lib/milk_tea/bindings/raw_bindings/defaults.rb +1338 -0
- data/lib/milk_tea/bindings/raw_bindings.rb +269 -0
- data/lib/milk_tea/bindings/steamworks.rb +629 -0
- data/lib/milk_tea/bindings/upstream_sources.rb +352 -0
- data/lib/milk_tea/bindings/vendored_box2d.rb +79 -0
- data/lib/milk_tea/bindings/vendored_c_library.rb +322 -0
- data/lib/milk_tea/bindings/vendored_cjson.rb +52 -0
- data/lib/milk_tea/bindings/vendored_flecs.rb +78 -0
- data/lib/milk_tea/bindings/vendored_glfw.rb +77 -0
- data/lib/milk_tea/bindings/vendored_libuv.rb +75 -0
- data/lib/milk_tea/bindings/vendored_pcre2.rb +84 -0
- data/lib/milk_tea/bindings/vendored_raylib.rb +131 -0
- data/lib/milk_tea/bindings/vendored_sdl3.rb +78 -0
- data/lib/milk_tea/bindings/vendored_steamworks.rb +71 -0
- data/lib/milk_tea/bindings/vendored_tool.rb +71 -0
- data/lib/milk_tea/bindings/vendored_tools.rb +23 -0
- data/lib/milk_tea/bindings/vendored_tracy.rb +37 -0
- data/lib/milk_tea/bindings.rb +23 -0
- data/lib/milk_tea/core/ast.rb +311 -0
- data/lib/milk_tea/core/async_runtime_installer.rb +28 -0
- data/lib/milk_tea/core/binding_types.rb +18 -0
- data/lib/milk_tea/core/bindings/attribute_binding.rb +58 -0
- data/lib/milk_tea/core/bindings/function_binding.rb +5 -0
- data/lib/milk_tea/core/bindings/module_binding.rb +58 -0
- data/lib/milk_tea/core/bindings/value_binding.rb +21 -0
- data/lib/milk_tea/core/c_backend/aggregate_utils.rb +103 -0
- data/lib/milk_tea/core/c_backend/control_flow_emission.rb +412 -0
- data/lib/milk_tea/core/c_backend/expressions.rb +468 -0
- data/lib/milk_tea/core/c_backend/feature_detection.rb +483 -0
- data/lib/milk_tea/core/c_backend/reachability.rb +398 -0
- data/lib/milk_tea/core/c_backend/reinterpret.rb +226 -0
- data/lib/milk_tea/core/c_backend/runtime_helpers.rb +1080 -0
- data/lib/milk_tea/core/c_backend/statements.rb +563 -0
- data/lib/milk_tea/core/c_backend/type_collectors.rb +1545 -0
- data/lib/milk_tea/core/c_backend/type_declaration.rb +223 -0
- data/lib/milk_tea/core/c_backend/type_system.rb +345 -0
- data/lib/milk_tea/core/c_backend.rb +287 -0
- data/lib/milk_tea/core/compatibility_helpers.rb +79 -0
- data/lib/milk_tea/core/compile_time/const_eval.rb +187 -0
- data/lib/milk_tea/core/compile_time.rb +329 -0
- data/lib/milk_tea/core/control_flow/builder.rb +582 -0
- data/lib/milk_tea/core/control_flow/constant_propagation.rb +143 -0
- data/lib/milk_tea/core/control_flow/dataflow.rb +74 -0
- data/lib/milk_tea/core/control_flow/definite_assignment.rb +79 -0
- data/lib/milk_tea/core/control_flow/graph.rb +90 -0
- data/lib/milk_tea/core/control_flow/liveness.rb +24 -0
- data/lib/milk_tea/core/control_flow/nullability_flow.rb +43 -0
- data/lib/milk_tea/core/control_flow/reachability.rb +22 -0
- data/lib/milk_tea/core/control_flow/termination.rb +31 -0
- data/lib/milk_tea/core/control_flow.rb +34 -0
- data/lib/milk_tea/core/cst.rb +48 -0
- data/lib/milk_tea/core/cst_builder.rb +19 -0
- data/lib/milk_tea/core/ir.rb +85 -0
- data/lib/milk_tea/core/keywords.rb +97 -0
- data/lib/milk_tea/core/lexer/character_classes.rb +60 -0
- data/lib/milk_tea/core/lexer/format_strings.rb +225 -0
- data/lib/milk_tea/core/lexer/heredocs.rb +199 -0
- data/lib/milk_tea/core/lexer/indentation.rb +62 -0
- data/lib/milk_tea/core/lexer/numbers.rb +96 -0
- data/lib/milk_tea/core/lexer/recovery.rb +32 -0
- data/lib/milk_tea/core/lexer/strings.rb +167 -0
- data/lib/milk_tea/core/lexer/symbols.rb +71 -0
- data/lib/milk_tea/core/lexer/trivia.rb +53 -0
- data/lib/milk_tea/core/lexer.rb +430 -0
- data/lib/milk_tea/core/lowering/artifacts.rb +26 -0
- data/lib/milk_tea/core/lowering/async/analysis.rb +245 -0
- data/lib/milk_tea/core/lowering/async/lowering.rb +1399 -0
- data/lib/milk_tea/core/lowering/async/normalization.rb +459 -0
- data/lib/milk_tea/core/lowering/async.rb +714 -0
- data/lib/milk_tea/core/lowering/block.rb +1052 -0
- data/lib/milk_tea/core/lowering/calls.rb +1565 -0
- data/lib/milk_tea/core/lowering/declarations.rb +214 -0
- data/lib/milk_tea/core/lowering/dyn.rb +206 -0
- data/lib/milk_tea/core/lowering/events.rb +1054 -0
- data/lib/milk_tea/core/lowering/expressions.rb +1645 -0
- data/lib/milk_tea/core/lowering/foreign_cstr.rb +206 -0
- data/lib/milk_tea/core/lowering/functions.rb +242 -0
- data/lib/milk_tea/core/lowering/loops.rb +1087 -0
- data/lib/milk_tea/core/lowering/lowering_context.rb +80 -0
- data/lib/milk_tea/core/lowering/proc.rb +419 -0
- data/lib/milk_tea/core/lowering/resolve.rb +2516 -0
- data/lib/milk_tea/core/lowering/scans.rb +220 -0
- data/lib/milk_tea/core/lowering/str_buffer.rb +125 -0
- data/lib/milk_tea/core/lowering/utils.rb +1453 -0
- data/lib/milk_tea/core/lowering.rb +378 -0
- data/lib/milk_tea/core/module_binder.rb +181 -0
- data/lib/milk_tea/core/module_loader/errors.rb +21 -0
- data/lib/milk_tea/core/module_loader.rb +479 -0
- data/lib/milk_tea/core/module_path_resolver.rb +153 -0
- data/lib/milk_tea/core/module_roots.rb +88 -0
- data/lib/milk_tea/core/parser/attributes.rb +71 -0
- data/lib/milk_tea/core/parser/blocks.rb +119 -0
- data/lib/milk_tea/core/parser/declarations.rb +749 -0
- data/lib/milk_tea/core/parser/expressions.rb +624 -0
- data/lib/milk_tea/core/parser/recovery.rb +131 -0
- data/lib/milk_tea/core/parser/statements.rb +756 -0
- data/lib/milk_tea/core/parser/types.rb +271 -0
- data/lib/milk_tea/core/parser.rb +400 -0
- data/lib/milk_tea/core/prelude_installer.rb +29 -0
- data/lib/milk_tea/core/pretty_printer/ast_formatter.rb +917 -0
- data/lib/milk_tea/core/pretty_printer/base_formatter.rb +87 -0
- data/lib/milk_tea/core/pretty_printer/ir_formatter.rb +300 -0
- data/lib/milk_tea/core/pretty_printer.rb +17 -0
- data/lib/milk_tea/core/semantic_analyzer/analysis_context.rb +314 -0
- data/lib/milk_tea/core/semantic_analyzer/attributes.rb +184 -0
- data/lib/milk_tea/core/semantic_analyzer/calls.rb +992 -0
- data/lib/milk_tea/core/semantic_analyzer/expressions.rb +1735 -0
- data/lib/milk_tea/core/semantic_analyzer/flow_refinement.rb +355 -0
- data/lib/milk_tea/core/semantic_analyzer/foreign_functions.rb +155 -0
- data/lib/milk_tea/core/semantic_analyzer/function_binding.rb +354 -0
- data/lib/milk_tea/core/semantic_analyzer/generics.rb +383 -0
- data/lib/milk_tea/core/semantic_analyzer/interface_conformance.rb +78 -0
- data/lib/milk_tea/core/semantic_analyzer/module_context.rb +35 -0
- data/lib/milk_tea/core/semantic_analyzer/name_resolution.rb +1438 -0
- data/lib/milk_tea/core/semantic_analyzer/nullability.rb +421 -0
- data/lib/milk_tea/core/semantic_analyzer/statements.rb +1308 -0
- data/lib/milk_tea/core/semantic_analyzer/top_level.rb +588 -0
- data/lib/milk_tea/core/semantic_analyzer/type_compatibility.rb +307 -0
- data/lib/milk_tea/core/semantic_analyzer/type_declaration.rb +851 -0
- data/lib/milk_tea/core/semantic_analyzer.rb +327 -0
- data/lib/milk_tea/core/token.rb +26 -0
- data/lib/milk_tea/core/token_stream.rb +30 -0
- data/lib/milk_tea/core/types/layout.rb +243 -0
- data/lib/milk_tea/core/types/predicates.rb +609 -0
- data/lib/milk_tea/core/types/registry.rb +83 -0
- data/lib/milk_tea/core/types/types.rb +1696 -0
- data/lib/milk_tea/core/types/visitor.rb +442 -0
- data/lib/milk_tea/core.rb +25 -0
- data/lib/milk_tea/dap/backends/lldb_dap.rb +158 -0
- data/lib/milk_tea/dap/protocol.rb +58 -0
- data/lib/milk_tea/dap/server/breakpoints.rb +66 -0
- data/lib/milk_tea/dap/server/debug_map.rb +291 -0
- data/lib/milk_tea/dap/server/handlers.rb +374 -0
- data/lib/milk_tea/dap/server/launch.rb +261 -0
- data/lib/milk_tea/dap/server/lldb_backend.rb +380 -0
- data/lib/milk_tea/dap/server/pause_diagnostics.rb +109 -0
- data/lib/milk_tea/dap/server/utilities.rb +160 -0
- data/lib/milk_tea/dap/server/wire.rb +55 -0
- data/lib/milk_tea/dap/server.rb +131 -0
- data/lib/milk_tea/dap/session.rb +152 -0
- data/lib/milk_tea/dap.rb +6 -0
- data/lib/milk_tea/lsp/dependency_resolution.rb +52 -0
- data/lib/milk_tea/lsp/diagnostics.rb +611 -0
- data/lib/milk_tea/lsp/protocol.rb +104 -0
- data/lib/milk_tea/lsp/server/call_hierarchy.rb +274 -0
- data/lib/milk_tea/lsp/server/code_actions.rb +446 -0
- data/lib/milk_tea/lsp/server/code_lens.rb +97 -0
- data/lib/milk_tea/lsp/server/completion.rb +1099 -0
- data/lib/milk_tea/lsp/server/configuration.rb +167 -0
- data/lib/milk_tea/lsp/server/debug_info.rb +45 -0
- data/lib/milk_tea/lsp/server/definition.rb +779 -0
- data/lib/milk_tea/lsp/server/diagnostics_scheduling.rb +239 -0
- data/lib/milk_tea/lsp/server/execute_command.rb +25 -0
- data/lib/milk_tea/lsp/server/folding_range.rb +153 -0
- data/lib/milk_tea/lsp/server/formatting.rb +575 -0
- data/lib/milk_tea/lsp/server/hover.rb +1465 -0
- data/lib/milk_tea/lsp/server/inlay_hints.rb +204 -0
- data/lib/milk_tea/lsp/server/lifecycle.rb +234 -0
- data/lib/milk_tea/lsp/server/linked_editing_range.rb +43 -0
- data/lib/milk_tea/lsp/server/on_type_formatting.rb +73 -0
- data/lib/milk_tea/lsp/server/progress.rb +47 -0
- data/lib/milk_tea/lsp/server/references.rb +433 -0
- data/lib/milk_tea/lsp/server/rename.rb +598 -0
- data/lib/milk_tea/lsp/server/selection_range.rb +130 -0
- data/lib/milk_tea/lsp/server/semantic_tokens.rb +1745 -0
- data/lib/milk_tea/lsp/server/signature_help.rb +200 -0
- data/lib/milk_tea/lsp/server/text_documents.rb +125 -0
- data/lib/milk_tea/lsp/server/type_hierarchy.rb +167 -0
- data/lib/milk_tea/lsp/server/utilities.rb +520 -0
- data/lib/milk_tea/lsp/server.rb +415 -0
- data/lib/milk_tea/lsp/workspace/analysis.rb +209 -0
- data/lib/milk_tea/lsp/workspace/caches.rb +260 -0
- data/lib/milk_tea/lsp/workspace/collection.rb +98 -0
- data/lib/milk_tea/lsp/workspace/definition_index.rb +184 -0
- data/lib/milk_tea/lsp/workspace/dependency_graph.rb +282 -0
- data/lib/milk_tea/lsp/workspace/store.rb +154 -0
- data/lib/milk_tea/lsp/workspace/utilities.rb +490 -0
- data/lib/milk_tea/lsp/workspace.rb +127 -0
- data/lib/milk_tea/lsp.rb +13 -0
- data/lib/milk_tea/packages/atomic_write.rb +63 -0
- data/lib/milk_tea/packages/dependency_solver.rb +194 -0
- data/lib/milk_tea/packages/graph.rb +139 -0
- data/lib/milk_tea/packages/lock.rb +421 -0
- data/lib/milk_tea/packages/manager_cli.rb +485 -0
- data/lib/milk_tea/packages/manifest.rb +356 -0
- data/lib/milk_tea/packages/manifest_editor.rb +134 -0
- data/lib/milk_tea/packages/registry_metadata_provider.rb +34 -0
- data/lib/milk_tea/packages/registry_store.rb +420 -0
- data/lib/milk_tea/packages/services.rb +41 -0
- data/lib/milk_tea/packages/source_cache.rb +71 -0
- data/lib/milk_tea/packages/source_fetcher.rb +124 -0
- data/lib/milk_tea/packages/source_resolver.rb +389 -0
- data/lib/milk_tea/packages/version.rb +164 -0
- data/lib/milk_tea/packages.rb +16 -0
- data/lib/milk_tea/tooling/asset_pack.rb +141 -0
- data/lib/milk_tea/tooling/build.rb +1124 -0
- data/lib/milk_tea/tooling/build_cache.rb +240 -0
- data/lib/milk_tea/tooling/cli.rb +2688 -0
- data/lib/milk_tea/tooling/cst_formatter.rb +13 -0
- data/lib/milk_tea/tooling/debug_info_formatter.rb +456 -0
- data/lib/milk_tea/tooling/debug_map.rb +248 -0
- data/lib/milk_tea/tooling/docs_app.rb +369 -0
- data/lib/milk_tea/tooling/error_formatter.rb +86 -0
- data/lib/milk_tea/tooling/formatter.rb +787 -0
- data/lib/milk_tea/tooling/linter/doc_tags.rb +212 -0
- data/lib/milk_tea/tooling/linter/fix_engine.rb +237 -0
- data/lib/milk_tea/tooling/linter/flow_rules.rb +380 -0
- data/lib/milk_tea/tooling/linter/imports_platform.rb +841 -0
- data/lib/milk_tea/tooling/linter/release_rules.rb +744 -0
- data/lib/milk_tea/tooling/linter/reserved_names.rb +199 -0
- data/lib/milk_tea/tooling/linter/rules.rb +593 -0
- data/lib/milk_tea/tooling/linter/source_helpers.rb +407 -0
- data/lib/milk_tea/tooling/linter/trailing_comma.rb +139 -0
- data/lib/milk_tea/tooling/linter/visitors.rb +1286 -0
- data/lib/milk_tea/tooling/linter.rb +798 -0
- data/lib/milk_tea/tooling/project_scaffold.rb +82 -0
- data/lib/milk_tea/tooling/public/css/docs.css +166 -0
- data/lib/milk_tea/tooling/public/js/docs.js +94 -0
- data/lib/milk_tea/tooling/run.rb +408 -0
- data/lib/milk_tea/tooling/templates/wasm_shell.html +48 -0
- data/lib/milk_tea/tooling/toolchain_cli.rb +157 -0
- data/lib/milk_tea/tooling/views/404.erb +7 -0
- data/lib/milk_tea/tooling/views/index.erb +87 -0
- data/lib/milk_tea/tooling/views/layout.erb +69 -0
- data/lib/milk_tea/tooling/views/module.erb +97 -0
- data/lib/milk_tea/tooling/views/stdlib.erb +21 -0
- data/lib/milk_tea/tooling.rb +20 -0
- data/lib/milk_tea.rb +8 -0
- metadata +426 -0
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cgi/escape"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
require "net/http"
|
|
6
|
+
require "rubygems/package"
|
|
7
|
+
require "stringio"
|
|
8
|
+
require "tmpdir"
|
|
9
|
+
require "uri"
|
|
10
|
+
require "zlib"
|
|
11
|
+
|
|
12
|
+
module MilkTea
|
|
13
|
+
class PackageRegistryStoreError < StandardError; end
|
|
14
|
+
|
|
15
|
+
class PackageRegistryStore
|
|
16
|
+
Result = Data.define(:package_name, :version, :path)
|
|
17
|
+
|
|
18
|
+
def self.default_root(env: ENV, home: nil)
|
|
19
|
+
explicit_root = env.fetch("MILK_TEA_PACKAGE_REGISTRY", "").to_s.strip
|
|
20
|
+
return File.expand_path(explicit_root) unless explicit_root.empty?
|
|
21
|
+
|
|
22
|
+
xdg_data_home = env.fetch("XDG_DATA_HOME", "").to_s.strip
|
|
23
|
+
base_root = if xdg_data_home.empty?
|
|
24
|
+
File.join(home || Dir.home, ".local", "share")
|
|
25
|
+
else
|
|
26
|
+
File.expand_path(xdg_data_home)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
File.join(base_root, "milk_tea", "registry")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.default_upstream_root(env: ENV)
|
|
33
|
+
explicit_root = env.fetch("MILK_TEA_PACKAGE_REGISTRY_UPSTREAM", "").to_s.strip
|
|
34
|
+
return nil if explicit_root.empty?
|
|
35
|
+
|
|
36
|
+
http_url?(explicit_root) ? explicit_root : File.expand_path(explicit_root)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.http_url?(value)
|
|
40
|
+
uri = URI.parse(value.to_s)
|
|
41
|
+
%w[http https].include?(uri.scheme)
|
|
42
|
+
rescue URI::InvalidURIError
|
|
43
|
+
false
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
attr_reader :root, :upstream_root
|
|
47
|
+
|
|
48
|
+
def initialize(root: self.class.default_root, upstream_root: self.class.default_upstream_root)
|
|
49
|
+
@root = File.expand_path(root)
|
|
50
|
+
@upstream_root = normalize_optional_root(upstream_root)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def package_root_for(package_or_identity, version = nil)
|
|
54
|
+
package_name, package_version = extract_package_version(package_or_identity, version)
|
|
55
|
+
|
|
56
|
+
File.join(@root, "packages", normalize_component(package_name, "package name"), normalize_component(package_version, "package version"))
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def manifest_path_for(package_or_identity, version = nil)
|
|
60
|
+
File.join(package_root_for(package_or_identity, version), "package.toml")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def published?(package_or_identity, version = nil)
|
|
64
|
+
File.file?(manifest_path_for(package_or_identity, version))
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def published_root_for(package_or_identity, version = nil, sync: false)
|
|
68
|
+
package_name, package_version = extract_package_version(package_or_identity, version)
|
|
69
|
+
sync(package_name, package_version) if sync && !published?(package_name, package_version) && upstream_configured?
|
|
70
|
+
manifest_path = manifest_path_for(package_name, package_version)
|
|
71
|
+
unless File.file?(manifest_path)
|
|
72
|
+
raise PackageRegistryStoreError,
|
|
73
|
+
"registry package #{package_name} version #{package_version} not found in #{@root}"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
package_root_for(package_name, package_version)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def available_versions(package_name)
|
|
80
|
+
versions = package_versions_for_root(@root, package_name)
|
|
81
|
+
if upstream_configured?
|
|
82
|
+
upstream_versions = if upstream_http?
|
|
83
|
+
package_versions_for_http(package_name)
|
|
84
|
+
else
|
|
85
|
+
package_versions_for_root(@upstream_root, package_name)
|
|
86
|
+
end
|
|
87
|
+
versions = upstream_versions + versions
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
versions.uniq
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def publish(path, target: :local)
|
|
94
|
+
manifest = PackageManifest.load(path)
|
|
95
|
+
package_version = manifest.package_version.to_s.strip
|
|
96
|
+
if package_version.empty?
|
|
97
|
+
raise PackageRegistryStoreError,
|
|
98
|
+
"package #{manifest.package_name} at #{manifest.manifest_path} must declare package.version before publishing"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
target_root = registry_root_for_target(target)
|
|
102
|
+
package_root = package_root_for_root(target_root, manifest.package_name, package_version)
|
|
103
|
+
if File.exist?(package_root)
|
|
104
|
+
raise PackageRegistryStoreError,
|
|
105
|
+
"package #{manifest.package_name} version #{package_version} already published in registry: #{package_root}"
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
materialize_package_copy!(manifest.root_dir, package_root)
|
|
109
|
+
write_registry_artifacts_with_rollback!(target_root, manifest.package_name, package_version, package_root)
|
|
110
|
+
|
|
111
|
+
Result.new(package_name: manifest.package_name, version: package_version, path: package_root)
|
|
112
|
+
rescue PackageManifestError => e
|
|
113
|
+
raise PackageRegistryStoreError, e.message
|
|
114
|
+
rescue SystemCallError => e
|
|
115
|
+
raise PackageRegistryStoreError, "failed to publish package to #{target_root || @root}: #{e.message}"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def sync(package_or_identity, version = nil)
|
|
119
|
+
package_name, package_version = extract_package_version(package_or_identity, version)
|
|
120
|
+
raise PackageRegistryStoreError, "registry upstream is not configured" unless upstream_configured?
|
|
121
|
+
|
|
122
|
+
local_root = package_root_for(package_name, package_version)
|
|
123
|
+
return Result.new(package_name:, version: package_version, path: local_root) if published?(package_name, package_version)
|
|
124
|
+
|
|
125
|
+
return sync_from_http(package_name, package_version, local_root) if upstream_http?
|
|
126
|
+
|
|
127
|
+
upstream_package_root = package_root_for_root(@upstream_root, package_name, package_version)
|
|
128
|
+
upstream_manifest_path = File.join(upstream_package_root, "package.toml")
|
|
129
|
+
unless File.file?(upstream_manifest_path)
|
|
130
|
+
raise PackageRegistryStoreError,
|
|
131
|
+
"registry package #{package_name} version #{package_version} not found in upstream registry #{@upstream_root}"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
upstream_manifest = PackageManifest.load(upstream_package_root)
|
|
135
|
+
if upstream_manifest.package_name != package_name || upstream_manifest.package_version != package_version
|
|
136
|
+
raise PackageRegistryStoreError,
|
|
137
|
+
"upstream registry package at #{upstream_manifest.manifest_path} resolved to #{upstream_manifest.package_name}@#{upstream_manifest.package_version.inspect}; expected #{package_name}@#{package_version}"
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
materialize_package_copy!(upstream_package_root, local_root)
|
|
141
|
+
write_registry_artifacts_with_rollback!(@root, package_name, package_version, local_root)
|
|
142
|
+
|
|
143
|
+
Result.new(package_name:, version: package_version, path: local_root)
|
|
144
|
+
rescue PackageManifestError => e
|
|
145
|
+
raise PackageRegistryStoreError, e.message
|
|
146
|
+
rescue SystemCallError => e
|
|
147
|
+
raise PackageRegistryStoreError, "failed to sync package from upstream registry #{@upstream_root}: #{e.message}"
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def upstream_configured?
|
|
151
|
+
!@upstream_root.nil?
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def upstream_http?
|
|
155
|
+
self.class.http_url?(@upstream_root)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
private
|
|
159
|
+
|
|
160
|
+
def extract_package_version(package_or_identity, version)
|
|
161
|
+
if package_or_identity.respond_to?(:package_name) && package_or_identity.respond_to?(:version)
|
|
162
|
+
[package_or_identity.package_name, package_or_identity.version]
|
|
163
|
+
else
|
|
164
|
+
[package_or_identity, version]
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def normalize_component(value, label)
|
|
169
|
+
text = value.to_s.strip
|
|
170
|
+
raise PackageRegistryStoreError, "#{label} cannot be empty" if text.empty?
|
|
171
|
+
|
|
172
|
+
separators = [File::SEPARATOR, File::ALT_SEPARATOR].compact
|
|
173
|
+
if separators.any? { |separator| text.include?(separator) } || text == "." || text == ".."
|
|
174
|
+
raise PackageRegistryStoreError, "#{label} contains an unsupported path separator: #{text.inspect}"
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
text
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def package_root_for_root(root, package_or_identity, version = nil)
|
|
181
|
+
package_name, package_version = extract_package_version(package_or_identity, version)
|
|
182
|
+
|
|
183
|
+
File.join(root, "packages", normalize_component(package_name, "package name"), normalize_component(package_version, "package version"))
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def archive_path_for_root(root, package_name, package_version)
|
|
187
|
+
package_dir = File.join(root, "packages", normalize_component(package_name, "package name"))
|
|
188
|
+
File.join(package_dir, "#{normalize_component(package_version, "package version")}.tar.gz")
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def versions_index_path_for_root(root, package_name)
|
|
192
|
+
File.join(root, "packages", normalize_component(package_name, "package name"), "versions.txt")
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def package_versions_for_root(root, package_name)
|
|
196
|
+
return [] if root.nil?
|
|
197
|
+
|
|
198
|
+
package_dir = File.join(root, "packages", normalize_component(package_name, "package name"))
|
|
199
|
+
return [] unless File.directory?(package_dir)
|
|
200
|
+
|
|
201
|
+
Dir.children(package_dir).sort.each_with_object([]) do |entry, versions|
|
|
202
|
+
next if entry.start_with?(".") || entry == "versions.txt" || entry.end_with?(".tar.gz")
|
|
203
|
+
|
|
204
|
+
manifest_path = File.join(package_dir, entry, "package.toml")
|
|
205
|
+
versions << entry if File.file?(manifest_path)
|
|
206
|
+
end
|
|
207
|
+
rescue SystemCallError => e
|
|
208
|
+
raise PackageRegistryStoreError, "failed to inspect registry package versions under #{package_dir}: #{e.message}"
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def package_versions_for_http(package_name)
|
|
212
|
+
response = http_get_response(package_versions_url(package_name))
|
|
213
|
+
return [] if response.is_a?(Net::HTTPNotFound)
|
|
214
|
+
raise_http_error!(response, "list registry package versions") unless response.is_a?(Net::HTTPSuccess)
|
|
215
|
+
|
|
216
|
+
response.body.lines.map(&:strip).reject(&:empty?)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def sync_from_http(package_name, package_version, local_root)
|
|
220
|
+
response = http_get_response(package_archive_url(package_name, package_version))
|
|
221
|
+
if response.is_a?(Net::HTTPNotFound)
|
|
222
|
+
raise PackageRegistryStoreError,
|
|
223
|
+
"registry package #{package_name} version #{package_version} not found in upstream registry #{@upstream_root}"
|
|
224
|
+
end
|
|
225
|
+
raise_http_error!(response, "download registry package archive") unless response.is_a?(Net::HTTPSuccess)
|
|
226
|
+
|
|
227
|
+
FileUtils.rm_rf(local_root) if File.exist?(local_root)
|
|
228
|
+
FileUtils.mkdir_p(File.dirname(local_root))
|
|
229
|
+
|
|
230
|
+
Dir.mktmpdir("milk-tea-http-registry", File.dirname(local_root)) do |tmpdir|
|
|
231
|
+
extract_archive_to_root(response.body, local_root, tmpdir)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
validate_synced_package!(local_root, package_name, package_version)
|
|
235
|
+
write_registry_artifacts_with_rollback!(@root, package_name, package_version, local_root)
|
|
236
|
+
|
|
237
|
+
Result.new(package_name:, version: package_version, path: local_root)
|
|
238
|
+
rescue PackageManifestError => e
|
|
239
|
+
raise PackageRegistryStoreError, e.message
|
|
240
|
+
rescue SystemCallError => e
|
|
241
|
+
raise PackageRegistryStoreError, "failed to sync package from upstream registry #{@upstream_root}: #{e.message}"
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def validate_synced_package!(package_root, package_name, package_version)
|
|
245
|
+
manifest = PackageManifest.load(package_root)
|
|
246
|
+
if manifest.package_name != package_name || manifest.package_version != package_version
|
|
247
|
+
raise PackageRegistryStoreError,
|
|
248
|
+
"upstream registry package at #{manifest.manifest_path} resolved to #{manifest.package_name}@#{manifest.package_version.inspect}; expected #{package_name}@#{package_version}"
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def write_registry_artifacts!(root, package_name, package_version, package_root)
|
|
253
|
+
write_package_archive!(archive_path_for_root(root, package_name, package_version), package_root)
|
|
254
|
+
write_versions_index!(root, package_name)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def write_registry_artifacts_with_rollback!(root, package_name, package_version, package_root)
|
|
258
|
+
write_registry_artifacts!(root, package_name, package_version, package_root)
|
|
259
|
+
rescue StandardError
|
|
260
|
+
cleanup_registry_entry!(root, package_name, package_version, package_root)
|
|
261
|
+
raise
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def write_package_archive!(archive_path, package_root)
|
|
265
|
+
tar_buffer = StringIO.new(+"")
|
|
266
|
+
Gem::Package::TarWriter.new(tar_buffer) do |tar|
|
|
267
|
+
add_directory_to_archive(tar, package_root, package_root)
|
|
268
|
+
end
|
|
269
|
+
tar_buffer.rewind
|
|
270
|
+
|
|
271
|
+
PackageAtomicWrite.open(archive_path, binmode: true) do |file|
|
|
272
|
+
gzip = Zlib::GzipWriter.new(file)
|
|
273
|
+
gzip.write(tar_buffer.string)
|
|
274
|
+
gzip.finish
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def add_directory_to_archive(tar, root_path, path)
|
|
279
|
+
Dir.children(path).sort.each do |entry|
|
|
280
|
+
next if entry.start_with?(".")
|
|
281
|
+
|
|
282
|
+
child_path = File.join(path, entry)
|
|
283
|
+
relative_path = child_path.delete_prefix(root_path + File::SEPARATOR)
|
|
284
|
+
stat = File.lstat(child_path)
|
|
285
|
+
if stat.directory?
|
|
286
|
+
tar.mkdir(relative_path, stat.mode)
|
|
287
|
+
add_directory_to_archive(tar, root_path, child_path)
|
|
288
|
+
elsif stat.file?
|
|
289
|
+
tar.add_file(relative_path, stat.mode) do |file|
|
|
290
|
+
File.open(child_path, "rb") do |source|
|
|
291
|
+
IO.copy_stream(source, file)
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def write_versions_index!(root, package_name)
|
|
299
|
+
path = versions_index_path_for_root(root, package_name)
|
|
300
|
+
versions = package_versions_for_root(root, package_name)
|
|
301
|
+
PackageAtomicWrite.write(path, versions.join("\n") + (versions.empty? ? "" : "\n"))
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def extract_archive_to_root(archive_body, destination_root, scratch_dir)
|
|
305
|
+
archive_path = File.join(scratch_dir, "package.tar.gz")
|
|
306
|
+
extract_root = File.join(File.dirname(archive_path), "extract")
|
|
307
|
+
File.binwrite(archive_path, archive_body)
|
|
308
|
+
FileUtils.mkdir_p(extract_root)
|
|
309
|
+
|
|
310
|
+
Zlib::GzipReader.open(archive_path) do |gzip|
|
|
311
|
+
Gem::Package::TarReader.new(gzip) do |tar|
|
|
312
|
+
tar.each do |entry|
|
|
313
|
+
relative_path = entry.full_name
|
|
314
|
+
next if relative_path.nil? || relative_path.empty?
|
|
315
|
+
|
|
316
|
+
destination_path = File.expand_path(relative_path, extract_root)
|
|
317
|
+
unless destination_path == extract_root || destination_path.start_with?(extract_root + File::SEPARATOR)
|
|
318
|
+
raise PackageRegistryStoreError, "registry package archive contains an invalid path #{relative_path.inspect}"
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
if entry.directory?
|
|
322
|
+
FileUtils.mkdir_p(destination_path)
|
|
323
|
+
elsif entry.file?
|
|
324
|
+
FileUtils.mkdir_p(File.dirname(destination_path))
|
|
325
|
+
File.open(destination_path, "wb", entry.header.mode) do |file|
|
|
326
|
+
IO.copy_stream(entry, file)
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
File.rename(extract_root, destination_root)
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
def materialize_package_copy!(source_root, destination_root)
|
|
337
|
+
staging_root = "#{destination_root}.tmp.#{$$}.#{Thread.current.object_id}"
|
|
338
|
+
|
|
339
|
+
FileUtils.rm_rf(staging_root)
|
|
340
|
+
FileUtils.mkdir_p(File.dirname(destination_root))
|
|
341
|
+
FileUtils.copy_entry(source_root, staging_root)
|
|
342
|
+
File.rename(staging_root, destination_root)
|
|
343
|
+
rescue StandardError
|
|
344
|
+
FileUtils.rm_rf(staging_root)
|
|
345
|
+
raise
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def cleanup_registry_entry!(root, package_name, package_version, package_root)
|
|
349
|
+
FileUtils.rm_rf(package_root)
|
|
350
|
+
FileUtils.rm_f(archive_path_for_root(root, package_name, package_version))
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def package_versions_url(package_name)
|
|
354
|
+
join_upstream_url("packages", package_name, "versions.txt")
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def package_archive_url(package_name, package_version)
|
|
358
|
+
join_upstream_url("packages", package_name, "#{package_version}.tar.gz")
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
def join_upstream_url(*segments)
|
|
362
|
+
encoded_segments = segments.map do |segment|
|
|
363
|
+
CGI.escape(segment.to_s).gsub("+", "%20")
|
|
364
|
+
end
|
|
365
|
+
base = @upstream_root.to_s.sub(%r{/+\z}, "")
|
|
366
|
+
"#{base}/#{encoded_segments.join("/")}"
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
def http_get_response(url, redirects_remaining = 5)
|
|
370
|
+
uri = URI.parse(url)
|
|
371
|
+
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |http|
|
|
372
|
+
http.request(Net::HTTP::Get.new(uri.request_uri))
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
if response.is_a?(Net::HTTPRedirection)
|
|
376
|
+
raise PackageRegistryStoreError, "too many HTTP redirects while fetching #{url}" if redirects_remaining <= 0
|
|
377
|
+
|
|
378
|
+
location = response["location"]
|
|
379
|
+
raise PackageRegistryStoreError, "missing redirect location while fetching #{url}" if location.to_s.strip.empty?
|
|
380
|
+
|
|
381
|
+
return http_get_response(URI.join(url, location).to_s, redirects_remaining - 1)
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
response
|
|
385
|
+
rescue URI::InvalidURIError => e
|
|
386
|
+
raise PackageRegistryStoreError, "invalid upstream registry URL #{url.inspect}: #{e.message}"
|
|
387
|
+
rescue SocketError, IOError, SystemCallError => e
|
|
388
|
+
raise PackageRegistryStoreError, "failed to fetch #{url}: #{e.message}"
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def raise_http_error!(response, action)
|
|
392
|
+
raise PackageRegistryStoreError,
|
|
393
|
+
"failed to #{action} from upstream registry #{@upstream_root}: HTTP #{response.code} #{response.message}"
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def normalize_optional_root(root)
|
|
397
|
+
text = root.to_s.strip
|
|
398
|
+
return nil if text.empty?
|
|
399
|
+
|
|
400
|
+
self.class.http_url?(text) ? text.sub(%r{/+\z}, "") : File.expand_path(text)
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
def registry_root_for_target(target)
|
|
404
|
+
case target
|
|
405
|
+
when :local
|
|
406
|
+
@root
|
|
407
|
+
when :upstream
|
|
408
|
+
raise PackageRegistryStoreError, "registry upstream is not configured" unless upstream_configured?
|
|
409
|
+
if upstream_http?
|
|
410
|
+
raise PackageRegistryStoreError,
|
|
411
|
+
"publishing directly to an HTTP registry upstream is not supported; publish to a filesystem mirror instead"
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
@upstream_root
|
|
415
|
+
else
|
|
416
|
+
raise PackageRegistryStoreError, "unknown registry publish target #{target.inspect}"
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
end
|
|
420
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MilkTea
|
|
4
|
+
class PackageServices
|
|
5
|
+
attr_reader :source_cache, :registry_store
|
|
6
|
+
|
|
7
|
+
def initialize(source_cache: PackageSourceCache.new, registry_store: PackageRegistryStore.new)
|
|
8
|
+
@source_cache = source_cache
|
|
9
|
+
@registry_store = registry_store
|
|
10
|
+
@source_resolvers = {}
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def source_fetcher
|
|
14
|
+
@source_fetcher ||= PackageSourceFetcher.new(
|
|
15
|
+
source_cache: source_cache,
|
|
16
|
+
source_resolver: source_resolver(:reject),
|
|
17
|
+
registry_store: registry_store,
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def source_resolver(mode)
|
|
22
|
+
@source_resolvers[mode] ||= case mode
|
|
23
|
+
when :reject
|
|
24
|
+
PackageSourceResolver.new(source_cache: source_cache)
|
|
25
|
+
when :cache
|
|
26
|
+
PackageSourceResolver.new(
|
|
27
|
+
source_cache: source_cache,
|
|
28
|
+
remote_resolution: :cache,
|
|
29
|
+
)
|
|
30
|
+
when :materialize
|
|
31
|
+
PackageSourceResolver.new(
|
|
32
|
+
source_cache: source_cache,
|
|
33
|
+
remote_resolution: :materialize,
|
|
34
|
+
source_fetcher: source_fetcher,
|
|
35
|
+
)
|
|
36
|
+
else
|
|
37
|
+
raise ArgumentError, "unknown dependency source resolution mode #{mode.inspect}"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MilkTea
|
|
4
|
+
class PackageSourceCacheError < StandardError; end
|
|
5
|
+
|
|
6
|
+
class PackageSourceCache
|
|
7
|
+
def self.default_root(env: ENV, home: nil)
|
|
8
|
+
xdg_cache_home = env.fetch("XDG_CACHE_HOME", "").to_s.strip
|
|
9
|
+
base_root = if xdg_cache_home.empty?
|
|
10
|
+
File.join(home || Dir.home, ".cache")
|
|
11
|
+
else
|
|
12
|
+
File.expand_path(xdg_cache_home)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
File.join(base_root, "milk_tea", "package_sources")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
attr_reader :root
|
|
19
|
+
|
|
20
|
+
def initialize(root: self.class.default_root)
|
|
21
|
+
@root = File.expand_path(root)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def path_for(source_or_identity)
|
|
25
|
+
identity = extract_identity(source_or_identity)
|
|
26
|
+
cache_key = identity.cache_key
|
|
27
|
+
unless cache_key
|
|
28
|
+
raise PackageSourceCacheError, "package source kind #{identity.kind.inspect} does not use the shared source cache"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
File.join(@root, *cache_key.split("/"))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def materialized_root_for(source_or_identity)
|
|
35
|
+
identity = extract_identity(source_or_identity)
|
|
36
|
+
base_path = path_for(identity)
|
|
37
|
+
|
|
38
|
+
if identity.respond_to?(:subdir) && identity.subdir
|
|
39
|
+
segments = identity.subdir.split(/[\\\/]+/).reject(&:empty?)
|
|
40
|
+
materialized_path = File.expand_path(File.join(base_path, *segments))
|
|
41
|
+
expanded_base_path = File.expand_path(base_path)
|
|
42
|
+
unless materialized_path == expanded_base_path || materialized_path.start_with?(expanded_base_path + File::SEPARATOR)
|
|
43
|
+
raise PackageSourceCacheError,
|
|
44
|
+
"git source subdir escapes the materialized package root: #{identity.subdir.inspect}"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
return materialized_path
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
base_path
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def manifest_path_for(source_or_identity)
|
|
54
|
+
File.join(materialized_root_for(source_or_identity), "package.toml")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def materialized?(source_or_identity)
|
|
58
|
+
File.file?(manifest_path_for(source_or_identity))
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def extract_identity(source_or_identity)
|
|
64
|
+
if source_or_identity.respond_to?(:identity)
|
|
65
|
+
source_or_identity.identity
|
|
66
|
+
else
|
|
67
|
+
source_or_identity
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "open3"
|
|
5
|
+
|
|
6
|
+
module MilkTea
|
|
7
|
+
class PackageSourceFetcherError < StandardError; end
|
|
8
|
+
|
|
9
|
+
class PackageSourceFetcher
|
|
10
|
+
Result = Data.define(:package_name, :identity, :status, :path)
|
|
11
|
+
|
|
12
|
+
def initialize(source_cache: PackageSourceCache.new, source_resolver: PackageSourceResolver.new(source_cache: source_cache), registry_store: PackageRegistryStore.new)
|
|
13
|
+
@source_cache = source_cache
|
|
14
|
+
@source_resolver = source_resolver
|
|
15
|
+
@registry_store = registry_store
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def fetch_locked_sources(path)
|
|
19
|
+
locked_packages = PackageLock.locked_packages(path, source_resolver: @source_resolver)
|
|
20
|
+
locked_packages.select { |package| package.identity.cacheable? }.map do |package|
|
|
21
|
+
fetch_locked_package(package)
|
|
22
|
+
end
|
|
23
|
+
rescue PackageLockError => e
|
|
24
|
+
raise PackageSourceFetcherError, e.message
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def materialize_identity(package_name:, identity:)
|
|
28
|
+
expected_manifest_path = @source_cache.manifest_path_for(identity)
|
|
29
|
+
|
|
30
|
+
case identity
|
|
31
|
+
when PackageSourceResolver::RegistryIdentity
|
|
32
|
+
fetch_registry_package(package_name, identity, expected_manifest_path)
|
|
33
|
+
when PackageSourceResolver::GitIdentity
|
|
34
|
+
fetch_git_package(package_name, identity, expected_manifest_path)
|
|
35
|
+
else
|
|
36
|
+
raise PackageSourceFetcherError,
|
|
37
|
+
"package source kind #{identity.kind.inspect} is not fetchable"
|
|
38
|
+
end
|
|
39
|
+
rescue PackageSourceCacheError => e
|
|
40
|
+
raise PackageSourceFetcherError, e.message
|
|
41
|
+
rescue PackageRegistryStoreError => e
|
|
42
|
+
raise PackageSourceFetcherError, e.message
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def fetch_locked_package(package)
|
|
48
|
+
identity = package.identity
|
|
49
|
+
expected_manifest_path = @source_cache.manifest_path_for(identity)
|
|
50
|
+
if package.manifest_path != expected_manifest_path
|
|
51
|
+
raise PackageSourceFetcherError,
|
|
52
|
+
"package.lock manifest_path for #{package.package_name} does not match the expected cache path #{expected_manifest_path}"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
materialize_identity(package_name: package.package_name, identity:)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def fetch_git_package(package_name, identity, expected_manifest_path)
|
|
59
|
+
checkout_root = @source_cache.path_for(identity)
|
|
60
|
+
materialized_root = @source_cache.materialized_root_for(identity)
|
|
61
|
+
|
|
62
|
+
if git_checkout_current?(checkout_root, identity.revision) && File.file?(expected_manifest_path)
|
|
63
|
+
return Result.new(package_name:, identity:, status: :present, path: materialized_root)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
FileUtils.rm_rf(checkout_root) if File.exist?(checkout_root)
|
|
67
|
+
FileUtils.mkdir_p(File.dirname(checkout_root))
|
|
68
|
+
|
|
69
|
+
run_git!("clone", identity.url, checkout_root)
|
|
70
|
+
run_git!("-C", checkout_root, "checkout", "--detach", identity.revision)
|
|
71
|
+
|
|
72
|
+
unless File.file?(expected_manifest_path)
|
|
73
|
+
raise PackageSourceFetcherError,
|
|
74
|
+
"git source for #{package_name} is missing package.toml at #{expected_manifest_path}"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
Result.new(package_name:, identity:, status: :materialized, path: materialized_root)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def fetch_registry_package(package_name, identity, expected_manifest_path)
|
|
81
|
+
cache_root = @source_cache.path_for(identity)
|
|
82
|
+
materialized_root = @source_cache.materialized_root_for(identity)
|
|
83
|
+
|
|
84
|
+
if File.file?(expected_manifest_path)
|
|
85
|
+
return Result.new(package_name:, identity:, status: :present, path: materialized_root)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
published_root = @registry_store.published_root_for(identity, sync: true)
|
|
89
|
+
|
|
90
|
+
FileUtils.rm_rf(cache_root) if File.exist?(cache_root)
|
|
91
|
+
FileUtils.mkdir_p(File.dirname(cache_root))
|
|
92
|
+
FileUtils.copy_entry(published_root, cache_root)
|
|
93
|
+
|
|
94
|
+
unless File.file?(expected_manifest_path)
|
|
95
|
+
raise PackageSourceFetcherError,
|
|
96
|
+
"registry source for #{package_name} is missing package.toml at #{expected_manifest_path}"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
Result.new(package_name:, identity:, status: :materialized, path: materialized_root)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def git_checkout_current?(checkout_root, revision)
|
|
103
|
+
return false unless File.directory?(checkout_root)
|
|
104
|
+
|
|
105
|
+
stdout, _stderr, status = Open3.capture3("git", "-C", checkout_root, "rev-parse", "HEAD")
|
|
106
|
+
return false unless status.success?
|
|
107
|
+
|
|
108
|
+
stdout.strip == revision
|
|
109
|
+
rescue Errno::ENOENT => e
|
|
110
|
+
raise PackageSourceFetcherError, "git not found while materializing package sources: #{e.message}"
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def run_git!(*args)
|
|
114
|
+
stdout, stderr, status = Open3.capture3("git", *args)
|
|
115
|
+
return if status.success?
|
|
116
|
+
|
|
117
|
+
details = [stdout, stderr].reject(&:empty?).join
|
|
118
|
+
raise PackageSourceFetcherError,
|
|
119
|
+
details.empty? ? "failed to materialize git package source" : "failed to materialize git package source:\n#{details}"
|
|
120
|
+
rescue Errno::ENOENT => e
|
|
121
|
+
raise PackageSourceFetcherError, "git not found while materializing package sources: #{e.message}"
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|