Class: MilkTea::Build
- Inherits:
-
Object
- Object
- MilkTea::Build
- Defined in:
- lib/milk_tea/tooling/build.rb
Defined Under Namespace
Classes: FrontendModule, Result, RubyFrontend
Constant Summary collapse
- DEFAULT_WASM_SHELL_TEMPLATE_PATH =
File.("templates/wasm_shell.html", __dir__).freeze
- WASM_SHELL_SCRIPT_PLACEHOLDER =
"{{{ SCRIPT }}}".freeze
- WASM_SHELL_CANVAS_PLACEHOLDER =
"{{{ MILK_TEA_CANVAS }}}".freeze
- WASM_SHELL_OUTPUT_PLACEHOLDER =
"{{{ MILK_TEA_OUTPUT }}}".freeze
- WASM_SHELL_BOOTSTRAP_PLACEHOLDER =
"{{{ MILK_TEA_BOOTSTRAP }}}".freeze
- WASM_SHELL_TEMPLATE =
File.read(DEFAULT_WASM_SHELL_TEMPLATE_PATH).freeze
- WASM_SHELL_CANVAS_TEMPLATE =
<<~HTML.freeze <canvas id="canvas" oncontextmenu="event.preventDefault()" tabindex="-1"></canvas> HTML
- WASM_SHELL_OUTPUT_TEMPLATE =
<<~HTML.freeze <pre id="output"></pre> HTML
- WASM_SHELL_BOOTSTRAP_TEMPLATE =
<<~HTML.freeze <script> var Module = { print: (function() { var element = document.getElementById('output'); if (element) element.textContent = ''; return function(text) { if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' '); console.log(text); if (element) { element.textContent += text + "\\n"; element.scrollTop = element.scrollHeight; } }; })(), printErr: (function() { var element = document.getElementById('output'); return function(text) { if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' '); console.error(text); if (element) { element.textContent += "[err] " + text + "\\n"; element.scrollTop = element.scrollHeight; } }; })(), canvas: (function() { return document.getElementById('canvas'); })() }; </script> HTML
Class Method Summary collapse
- .build(path, output_path: nil, cc: ENV.fetch("CC", "cc"), keep_c_path: nil, raw_bindings: nil, module_roots: nil, package_graph: nil, frontend: nil, debug: false, profile: nil, platform: nil, bundle: false, archive: false, no_cache: false, sanitize: false, kind: :executable, debug_guards: nil) ⇒ Object
- .clean(path, output_path: nil, profile: nil, platform: nil, bundle: false, archive: false) ⇒ Object
- .default_raw_bindings(root: MilkTea.root) ⇒ Object
- .ensure_program_has_entrypoint!(program, ir_program) ⇒ Object
- .frontend_build_artifacts(program, emit_line_directives: false, binary_path: nil, debug_guards: false) ⇒ Object
- .frontend_modules(program) ⇒ Object
Instance Method Summary collapse
- #add_archive_tree(tar, source_path, archive_path) ⇒ Object
- #artifact_extension ⇒ Object
- #build ⇒ Object
- #build_cached ⇒ Object
- #build_shared_library ⇒ Object
- #build_static_library ⇒ Object
- #build_uncached ⇒ Object
- #bundle_archive_path ⇒ Object
- #cache_cache_dir ⇒ Object
- #clean ⇒ Object
- #clean_bundle_archive ⇒ Object
- #clean_cache ⇒ Object
- #clean_output_artifacts(target_path) ⇒ Object
- #clean_staged_runtime_assets(target_path) ⇒ Object
- #clean_target_path ⇒ Object
- #collect_compiler_flags(frontend_modules) ⇒ Object
- #collect_link_flags(frontend_modules) ⇒ Object
- #compile(c_path, compiler_flags, link_flags) ⇒ Object
- #compile_and_link_cached(compiled_c, compiler_flags, link_flags, cache) ⇒ Object
- #compile_frontend(program, cache, emit_line_directives) ⇒ Object
- #compile_frontend_saved_c(compiled_c, emit_line_directives) ⇒ Object
- #compile_generated_c(source, compiler_flags, link_flags) ⇒ Object
- #compile_or_cache_object(cache, source, pic: false) ⇒ Object
- #compile_to_object_at(source, output_path, pic: false) ⇒ Object
- #compile_wasm(c_path, compiler_flags, link_flags) ⇒ Object
- #compiler_available?(compiler) ⇒ Boolean
- #compiler_command_name(compiler) ⇒ Object
- #compute_method_defs_hash(program) ⇒ Object
- #darwin_target? ⇒ Boolean
- #debug_guards_enabled? ⇒ Boolean
- #default_package_bundle_root ⇒ Object
- #default_package_output_path ⇒ Object
- #default_source_output_path(source_path) ⇒ Object
- #detect_module_changes(program, cache, source_files) ⇒ Object
- #emscripten_backend?(compiler) ⇒ Boolean
- #ensure_compiler_available! ⇒ Object
- #ensure_runtime_assets_do_not_overlap_source!(source_path, staged_path) ⇒ Object
- #ensure_supported_backend! ⇒ Object
- #host_platform ⇒ Object
-
#initialize(path, output_path:, cc:, keep_c_path:, raw_bindings:, module_roots: nil, package_graph: nil, frontend: nil, debug: false, profile: nil, platform: nil, bundle: false, archive: false, no_cache: false, sanitize: false, kind: :executable, debug_guards: nil) ⇒ Build
constructor
A new instance of Build.
- #line_directives_required? ⇒ Boolean
- #load_program_with_cache(cache) ⇒ Object
- #load_wasm_shell_template_source ⇒ Object
- #msvc_style_backend?(compiler) ⇒ Boolean
- #normalize_output_path(path) ⇒ Object
- #normalize_platform(value) ⇒ Object
- #normalize_profile(value) ⇒ Object
- #package_manifest_required_for?(path) ⇒ Boolean
- #path_within?(path, root) ⇒ Boolean
- #prepare_bindings(frontend_modules) ⇒ Object
- #prepare_program(cache:, emit_line_directives:) ⇒ Object
- #profile_compiler_flags ⇒ Object
- #render_wasm_shell_template ⇒ Object
- #resolve_compiler(requested_cc) ⇒ Object
- #reverse_import_graph(program) ⇒ Object
- #runtime_asset_mappings_for(target_path) ⇒ Object
- #runtime_asset_pack_path_for(target_path) ⇒ Object
- #runtime_assets_packed? ⇒ Boolean
- #sanitize_flags ⇒ Object
- #stage_runtime_assets ⇒ Object
- #target_wasm? ⇒ Boolean
- #target_windows? ⇒ Boolean
- #update_module_caches(program, cache, source_files) ⇒ Object
- #use_package_build_for?(path) ⇒ Boolean
- #validate_bundle_mode! ⇒ Object
- #validate_wasm_shell_placeholder!(template_source, placeholder, label) ⇒ Object
- #wasm_asset_flags ⇒ Object
- #with_wasm_shell_file ⇒ Object
- #write_bundle_archive ⇒ Object
- #write_c_file(path, source) ⇒ Object
Constructor Details
#initialize(path, output_path:, cc:, keep_c_path:, raw_bindings:, module_roots: nil, package_graph: nil, frontend: nil, debug: false, profile: nil, platform: nil, bundle: false, archive: false, no_cache: false, sanitize: false, kind: :executable, debug_guards: nil) ⇒ Build
Returns a new instance of Build.
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 |
# File 'lib/milk_tea/tooling/build.rb', line 130 def initialize(path, output_path:, cc:, keep_c_path:, raw_bindings:, module_roots: nil, package_graph: nil, frontend: nil, debug: false, profile: nil, platform: nil, bundle: false, archive: false, no_cache: false, sanitize: false, kind: :executable, debug_guards: nil) @kind = case kind when :executable, :static, :shared then kind else raise BuildError, "unknown build kind #{kind}; expected executable|static|shared" end @explicit_output_path = !output_path.nil? resolved_source = use_package_build_for?(path) if resolved_source @package_build = true manifest = resolved_source[:manifest] @source_path = manifest.source_path @project_root = manifest.root_dir @package_name = manifest.package_name @archive = archive @bundle = bundle || archive if manifest.package_kind == :library && @kind == :executable raise BuildError, "cannot build library package #{manifest.package_name} as an executable" end unless @source_path raise BuildError, "application package #{manifest.package_name} has no build entry; set build.entry or create src/main.mt" end @profile = normalize_profile(profile || manifest.profile || (debug ? :debug : :debug)) @platform = normalize_platform(platform || manifest.platform || host_platform) @resolved_source_path = ModuleLoader.resolve_source_path(@source_path, platform: @platform, error_class: BuildError) validate_bundle_mode! @manifest_output_path = manifest.output_path if @bundle @bundle_root = File.(output_path || manifest.output_path || default_package_bundle_root) @output_path = File.join(@bundle_root, "#{@package_name}#{artifact_extension}") else @bundle_root = nil resolved_output = output_path || manifest.output_path || default_package_output_path @output_path = normalize_output_path(File.(resolved_output)) end @assets_paths = manifest.assets_paths @html_template_path = manifest.html_template_path else @package_build = false @source_path = File.(path) @project_root = File.dirname(@source_path) @package_name = File.basename(@project_root).tr("-", "_") @archive = archive @bundle = bundle || archive @profile = normalize_profile(profile || (debug ? :debug : :debug)) @platform = normalize_platform(platform || host_platform) @resolved_source_path = ModuleLoader.resolve_source_path(@source_path, platform: @platform, error_class: BuildError) validate_bundle_mode! @manifest_output_path = nil @bundle_root = nil @output_path = normalize_output_path(File.(output_path || default_source_output_path(@source_path))) @assets_paths = [] @html_template_path = nil end @cc = resolve_compiler(cc) @keep_c_path = keep_c_path ? File.(keep_c_path) : nil @raw_bindings = raw_bindings @package_graph = package_graph @module_roots = (module_roots || @package_graph&.source_roots || MilkTea::ModuleRoots.roots_for_path(@source_path)).dup @frontend = frontend || RubyFrontend.new @no_cache = no_cache || sanitize @sanitize = sanitize @debug = debug @debug_guards = debug_guards end |
Class Method Details
.build(path, output_path: nil, cc: ENV.fetch("CC", "cc"), keep_c_path: nil, raw_bindings: nil, module_roots: nil, package_graph: nil, frontend: nil, debug: false, profile: nil, platform: nil, bundle: false, archive: false, no_cache: false, sanitize: false, kind: :executable, debug_guards: nil) ⇒ Object
73 74 75 76 |
# File 'lib/milk_tea/tooling/build.rb', line 73 def self.build(path, output_path: nil, cc: ENV.fetch("CC", "cc"), keep_c_path: nil, raw_bindings: nil, module_roots: nil, package_graph: nil, frontend: nil, debug: false, profile: nil, platform: nil, bundle: false, archive: false, no_cache: false, sanitize: false, kind: :executable, debug_guards: nil) raw_bindings ||= default_raw_bindings new(path, output_path:, cc:, keep_c_path:, raw_bindings:, module_roots:, package_graph:, frontend:, debug:, profile:, platform:, bundle:, archive:, no_cache:, sanitize:, kind:, debug_guards:).build end |
.clean(path, output_path: nil, profile: nil, platform: nil, bundle: false, archive: false) ⇒ Object
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/milk_tea/tooling/build.rb', line 78 def self.clean(path, output_path: nil, profile: nil, platform: nil, bundle: false, archive: false) new( path, output_path:, cc: ENV.fetch("CC", "cc"), keep_c_path: nil, raw_bindings: nil, module_roots: nil, debug: false, profile:, platform:, bundle:, archive: ).clean end |
.default_raw_bindings(root: MilkTea.root) ⇒ Object
124 125 126 127 128 |
# File 'lib/milk_tea/tooling/build.rb', line 124 def self.default_raw_bindings(root: MilkTea.root) require_relative "../bindings" RawBindings.default_registry(root:) end |
.ensure_program_has_entrypoint!(program, ir_program) ⇒ Object
650 651 652 653 654 655 656 657 658 659 |
# File 'lib/milk_tea/tooling/build.rb', line 650 def self.ensure_program_has_entrypoint!(program, ir_program) return if ir_program.functions.any?(&:entry_point) return if program.is_a?(IR::Program) if program.root_analysis.functions.key?("main") raise BuildError, "root main is not a valid executable entrypoint; expected `function main() -> int|void`, `function main(argc: int, argv: ptr[cstr]) -> int|void`, `function main(argc: int, argv: ptr[ptr[char]]) -> int|void`, or `function main(args: span[str]) -> int|void`" end raise BuildError, "no executable entrypoint found; define `main` with one of the supported executable signatures" end |
.frontend_build_artifacts(program, emit_line_directives: false, binary_path: nil, debug_guards: false) ⇒ Object
94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/milk_tea/tooling/build.rb', line 94 def self.frontend_build_artifacts(program, emit_line_directives: false, binary_path: nil, debug_guards: false) ir_program = program.is_a?(IR::Program) ? program : Lowering.lower(program) ensure_program_has_entrypoint!(program, ir_program) compiled_c = CBackend.emit(ir_program, emit_line_directives:, debug_guards:) saved_c = emit_line_directives ? nil : compiled_c { ir_program: ir_program, compiled_c: compiled_c, saved_c: saved_c, debug_map: binary_path ? DebugMap.from_ir(ir_program, binary_path:) : nil, modules: frontend_modules(program), } end |
.frontend_modules(program) ⇒ Object
109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
# File 'lib/milk_tea/tooling/build.rb', line 109 def self.frontend_modules(program) return [].freeze if program.is_a?(IR::Program) program.analyses_by_module_name.keys.sort.map do |module_name| analysis = program.analyses_by_module_name.fetch(module_name) FrontendModule.new( name: module_name, kind: analysis.module_kind, link_libraries: analysis.directives.grep(AST::LinkDirective).map(&:value).freeze, compiler_flags: analysis.directives.grep(AST::CompilerFlagDirective).map(&:value).freeze, uses_parallel_for: analysis.uses_parallel_for, ) end.freeze end |
Instance Method Details
#add_archive_tree(tar, source_path, archive_path) ⇒ Object
1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 |
# File 'lib/milk_tea/tooling/build.rb', line 1017 def add_archive_tree(tar, source_path, archive_path) stat = File.lstat(source_path) if stat.directory? tar.mkdir(archive_path, stat.mode & 0o777) Dir.children(source_path).sort.each do |child| add_archive_tree(tar, File.join(source_path, child), File.join(archive_path, child)) end elsif stat.file? tar.add_file(archive_path, stat.mode & 0o777) do |io| File.open(source_path, "rb") do |file| IO.copy_stream(file, io) end end end end |
#artifact_extension ⇒ Object
1075 1076 1077 1078 1079 1080 |
# File 'lib/milk_tea/tooling/build.rb', line 1075 def artifact_extension return ".html" if target_wasm? return ".exe" if target_windows? "" end |
#build ⇒ Object
230 231 232 233 234 235 236 237 238 239 240 241 242 243 |
# File 'lib/milk_tea/tooling/build.rb', line 230 def build Types::Registry.reset! ensure_compiler_available! ensure_supported_backend! return build_static_library if @kind == :static return build_shared_library if @kind == :shared if @frontend.is_a?(RubyFrontend) && !@no_cache build_cached else build_uncached end end |
#build_cached ⇒ Object
351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 |
# File 'lib/milk_tea/tooling/build.rb', line 351 def build_cached cache = BuildCache.new(root: MilkTea.root) emit_line_directives = line_directives_required? @cached = true program = load_program_with_cache(cache) compiled_c, frontend_modules, debug_map_source = compile_frontend(program, cache, emit_line_directives) prepare_bindings(frontend_modules) compiler_flags = collect_compiler_flags(frontend_modules) link_flags = collect_link_flags(frontend_modules) debug_map_path = DebugMap.sidecar_path_for(@output_path) FileUtils.mkdir_p(File.dirname(@output_path)) saved_c = compile_frontend_saved_c(compiled_c, emit_line_directives) if @keep_c_path saved_c ||= compiled_c unless emit_line_directives if emit_line_directives && saved_c.nil? saved_c = CBackend.emit(Lowering.lower(program), emit_line_directives: false, debug_guards: false) end raise BuildError, "frontend did not provide saved C output for --keep-c debug build" if saved_c.nil? write_c_file(@keep_c_path, saved_c) compile_and_link_cached(compiled_c, compiler_flags, link_flags, cache) if debug_map_source DebugMap.from_ir(debug_map_source, binary_path: @output_path).write(debug_map_path) end stage_runtime_assets archive_path = write_bundle_archive return Result.new(output_path: @output_path, c_path: @keep_c_path, compiler: @cc, link_flags:, profile: @profile, platform: @platform, bundle_root: @bundle_root, archive_path:, cached: @cached) end compile_and_link_cached(compiled_c, compiler_flags, link_flags, cache) if debug_map_source debug_map = debug_map_source.is_a?(IR::Program) ? DebugMap.from_ir(debug_map_source, binary_path: @output_path) : debug_map_source debug_map.write(debug_map_path) end stage_runtime_assets archive_path = write_bundle_archive Result.new(output_path: @output_path, c_path: nil, compiler: @cc, link_flags:, profile: @profile, platform: @platform, bundle_root: @bundle_root, archive_path:, cached: @cached) end |
#build_shared_library ⇒ Object
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 |
# File 'lib/milk_tea/tooling/build.rb', line 261 def build_shared_library cache = @no_cache ? nil : BuildCache.new(root: MilkTea.root) emit_line_directives = line_directives_required? _source_files, program, compiled_c, saved_c, frontend_modules, _ir_program = prepare_program(cache:, emit_line_directives:) prepare_bindings(frontend_modules) compiler_flags = collect_compiler_flags(frontend_modules) link_flags = collect_link_flags(frontend_modules) object_path = compile_or_cache_object(cache, compiled_c || saved_c, pic: true) shared_flags = darwin_target? ? ["-dynamiclib"] : ["-shared"] command = [@cc, *shared_flags, "-o", @output_path, object_path, *link_flags, *compiler_flags] stdout, stderr, status = Open3.capture3(*command) raise BuildError, "shared library link failed:\n#{stdout}#{stderr}" unless status.success? generate_header(program) if program Result.new(output_path: @output_path, c_path: nil, compiler: @cc, link_flags:, profile: @profile, platform: @platform, bundle_root: nil, archive_path: nil, cached: false) end |
#build_static_library ⇒ Object
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 |
# File 'lib/milk_tea/tooling/build.rb', line 245 def build_static_library cache = @no_cache ? nil : BuildCache.new(root: MilkTea.root) emit_line_directives = line_directives_required? _source_files, program, compiled_c, saved_c, frontend_modules, _ir_program = prepare_program(cache:, emit_line_directives:) prepare_bindings(frontend_modules) object_path = compile_or_cache_object(cache, compiled_c || saved_c) archive_path = @output_path command = darwin_target? ? ["libtool", "-static", "-o", archive_path, object_path] : ["ar", "rcs", archive_path, object_path] stdout, stderr, status = Open3.capture3(*command) raise BuildError, "archiver failed:\n#{stdout}#{stderr}" unless status.success? generate_header(program) if program Result.new(output_path: @output_path, c_path: nil, compiler: @cc, link_flags: [], profile: @profile, platform: @platform, bundle_root: nil, archive_path: nil, cached: false) end |
#build_uncached ⇒ Object
398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 |
# File 'lib/milk_tea/tooling/build.rb', line 398 def build_uncached emit_line_directives = line_directives_required? artifacts = @frontend.compile( path: @resolved_source_path, module_roots: @module_roots, package_graph: @package_graph, platform: @platform, emit_line_directives: emit_line_directives, binary_path: @output_path, debug_guards: debug_guards_enabled?, ) ir_program = artifacts[:ir_program] compiled_c = artifacts.fetch(:compiled_c) saved_c = artifacts[:saved_c] debug_map = artifacts.fetch(:debug_map) frontend_modules = artifacts.fetch(:modules) prepare_bindings(frontend_modules) compiler_flags = collect_compiler_flags(frontend_modules) link_flags = collect_link_flags(frontend_modules) debug_map_path = DebugMap.sidecar_path_for(@output_path) FileUtils.mkdir_p(File.dirname(@output_path)) if @keep_c_path saved_c ||= compiled_c unless emit_line_directives saved_c ||= CBackend.emit(ir_program, emit_line_directives: false, debug_guards: false) if emit_line_directives && ir_program raise BuildError, "frontend did not provide saved C output for --keep-c debug build" if saved_c.nil? write_c_file(@keep_c_path, saved_c) if emit_line_directives compile_generated_c(compiled_c, compiler_flags, link_flags) else compile(@keep_c_path, compiler_flags, link_flags) end debug_map.write(debug_map_path) stage_runtime_assets archive_path = write_bundle_archive return Result.new(output_path: @output_path, c_path: @keep_c_path, compiler: @cc, link_flags:, profile: @profile, platform: @platform, bundle_root: @bundle_root, archive_path:, cached: false) end compile_generated_c(compiled_c, compiler_flags, link_flags) debug_map.write(debug_map_path) stage_runtime_assets archive_path = write_bundle_archive Result.new(output_path: @output_path, c_path: nil, compiler: @cc, link_flags:, profile: @profile, platform: @platform, bundle_root: @bundle_root, archive_path:, cached: false) end |
#bundle_archive_path ⇒ Object
1013 1014 1015 |
# File 'lib/milk_tea/tooling/build.rb', line 1013 def bundle_archive_path "#{@bundle_root}.tar.gz" end |
#cache_cache_dir ⇒ Object
343 344 345 |
# File 'lib/milk_tea/tooling/build.rb', line 343 def cache_cache_dir File.join(MilkTea.data_root.to_s, "tmp", "mtc-cache") end |
#clean ⇒ Object
211 212 213 214 215 216 217 218 219 220 221 222 223 |
# File 'lib/milk_tea/tooling/build.rb', line 211 def clean target_path = clean_target_path if File.directory?(target_path) FileUtils.rm_rf(target_path) else clean_output_artifacts(target_path) clean_staged_runtime_assets(target_path) FileUtils.rm_f(DebugMap.sidecar_path_for(@output_path)) end clean_bundle_archive clean_cache target_path end |
#clean_bundle_archive ⇒ Object
886 887 888 889 890 |
# File 'lib/milk_tea/tooling/build.rb', line 886 def clean_bundle_archive return unless @archive FileUtils.rm_f(bundle_archive_path) end |
#clean_cache ⇒ Object
225 226 227 228 |
# File 'lib/milk_tea/tooling/build.rb', line 225 def clean_cache cache_dir = File.join(MilkTea.data_root.to_s, "tmp", "mtc-cache") FileUtils.rm_rf(cache_dir) if File.exist?(cache_dir) end |
#clean_output_artifacts(target_path) ⇒ Object
876 877 878 879 880 881 882 883 884 |
# File 'lib/milk_tea/tooling/build.rb', line 876 def clean_output_artifacts(target_path) FileUtils.rm_f(target_path) return unless target_wasm? && File.extname(target_path) == ".html" base_path = target_path.sub(/\.html\z/, "") %w[.js .wasm .data .worker.js .symbols .wasm.map].each do |extension| FileUtils.rm_f("#{base_path}#{extension}") end end |
#clean_staged_runtime_assets(target_path) ⇒ Object
892 893 894 895 896 897 898 |
# File 'lib/milk_tea/tooling/build.rb', line 892 def clean_staged_runtime_assets(target_path) runtime_asset_mappings_for(target_path).each do |_source_path, staged_path| FileUtils.rm_rf(staged_path) end FileUtils.rm_f(runtime_asset_pack_path_for(target_path)) if runtime_assets_packed? end |
#clean_target_path ⇒ Object
447 448 449 450 451 452 453 454 455 456 457 |
# File 'lib/milk_tea/tooling/build.rb', line 447 def clean_target_path if @package_build && !@explicit_output_path && @manifest_output_path.nil? return File.join(@project_root, "build", "dist") if @bundle File.join(@project_root, "build") elsif @bundle @bundle_root else @output_path end end |
#collect_compiler_flags(frontend_modules) ⇒ Object
817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 |
# File 'lib/milk_tea/tooling/build.rb', line 817 def collect_compiler_flags(frontend_modules) std_c_include_flag = "-I#{MilkTea.root.join('std/c')}" flags = frontend_modules.each_with_object([]) do |mod, result| next unless mod.kind == :raw_module if mod.name.start_with?("std.c.") result << std_c_include_flag unless result.include?(std_c_include_flag) end mod.compiler_flags.each do |compiler_flag| result << compiler_flag unless result.include?(compiler_flag) end binding = @raw_bindings.find_by_module_name(mod.name) next unless binding binding.build_flags(platform: @platform).each do |flag| result << flag unless result.include?(flag) end end if frontend_modules.any?(&:uses_parallel_for) flags << std_c_include_flag unless flags.include?(std_c_include_flag) libuv_binding = @raw_bindings.find_by_module_name("std.c.libuv") if libuv_binding libuv_binding.build_flags(platform: @platform).each do |flag| flags << flag unless flags.include?(flag) end end end flags rescue RawBindings::Error => e raise BuildError, e. end |
#collect_link_flags(frontend_modules) ⇒ Object
781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 |
# File 'lib/milk_tea/tooling/build.rb', line 781 def collect_link_flags(frontend_modules) flags = frontend_modules.each_with_object([]) do |mod, flags| next unless mod.kind == :raw_module binding = @raw_bindings.find_by_module_name(mod.name) if binding binding.link_flags(platform: @platform).grep(/\A-L/).each do |flag| flags << flag unless flags.include?(flag) end end mod.link_libraries.each do |link_library| flag = "-l#{link_library}" flags << flag unless flags.include?(flag) end if binding binding.link_flags(platform: @platform).reject { |flag| flag.start_with?("-L") }.each do |flag| flags << flag unless flags.include?(flag) end end end if frontend_modules.any?(&:uses_parallel_for) && !flags.include?("-luv") libuv_binding = @raw_bindings.find_by_module_name("std.c.libuv") if libuv_binding libuv_binding.link_flags(platform: @platform).each do |flag| flags << flag unless flags.include?(flag) end end flags << "-luv" unless flags.include?("-luv") end flags end |
#compile(c_path, compiler_flags, link_flags) ⇒ Object
854 855 856 857 858 859 860 861 862 863 864 865 866 |
# File 'lib/milk_tea/tooling/build.rb', line 854 def compile(c_path, compiler_flags, link_flags) return compile_wasm(c_path, compiler_flags, link_flags) if target_wasm? profile_flags = profile_compiler_flags command = [@cc, "-std=c11", *sanitize_flags, *profile_flags, *compiler_flags, c_path, "-o", @output_path, *link_flags] stdout, stderr, status = Open3.capture3(*command) return if status.success? details = [stdout, stderr].reject(&:empty?).join raise BuildError, details.empty? ? "C compiler failed" : "C compiler failed:\n#{details}" rescue Errno::ENOENT raise BuildError, "C compiler not found: #{@cc}" end |
#compile_and_link_cached(compiled_c, compiler_flags, link_flags, cache) ⇒ Object
636 637 638 639 640 641 642 643 644 645 646 647 648 |
# File 'lib/milk_tea/tooling/build.rb', line 636 def compile_and_link_cached(compiled_c, compiler_flags, link_flags, cache) binary_key = cache.binary_key(c_source: compiled_c, cc: @cc, compiler_flags:, link_flags:) cached_binary = cache.fetch_binary(binary_key) if cached_binary FileUtils.cp(cached_binary, @output_path) return end @cached = false compile_generated_c(compiled_c, compiler_flags, link_flags) cache.store_binary(binary_key, @output_path) end |
#compile_frontend(program, cache, emit_line_directives) ⇒ Object
476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 |
# File 'lib/milk_tea/tooling/build.rb', line 476 def compile_frontend(program, cache, emit_line_directives) source_files = program.analyses_by_path.keys.each_with_object({}) do |path, hash| hash[path] = File.read(path, mode: "rb") end key = cache.program_key(source_files: source_files.to_a) cached = cache.fetch_program(key) if cached return [cached.c_source, cached.frontend_modules, nil] end @cached = false method_defs_hash = compute_method_defs_hash(program) previous_method_defs = cache.fetch_method_defs if previous_method_defs && previous_method_defs != method_defs_hash cache.invalidate_all_module_ir end module_changes = detect_module_changes(program, cache, source_files) if module_changes.any? { |_, v| v == :changed } changed = module_changes.select { |_, v| v == :changed }.keys warn " #{changed.length} of #{module_changes.length} module(s) changed" if $VERBOSE || ENV["MTC_VERBOSE"] end rgraph = reverse_import_graph(program) changed_names = module_changes.select { |_, v| v == :changed }.map { |k, _| k.to_s } invalidated = {} changed_names.each { |mod| invalidated[mod] = true } changed_names.each do |mod| (rgraph[mod] || []).each { |dep| invalidated[dep] = true } end preload = {} preload_synthetics = {} module_changes.each do |module_name, status| next unless status == :unchanged next if invalidated.key?(module_name.to_s) path = program.analyses_by_path.keys.find { |p| program.analyses_by_path[p].module_name.to_s == module_name.to_s } next unless path module_content = source_files[path] next unless module_content path_key = cache.module_key(path, module_content) cached_data = cache.fetch_module_ir(path_key) next unless cached_data if cached_data.is_a?(Array) ir, synths = cached_data preload[module_name.to_s] = ir preload_synthetics[module_name.to_s] = synths || {} end end ir_program, modules, per_module_synthetics = Lowering.lower_incremental( program, cached: preload.empty? ? nil : preload, cached_synthetics: preload_synthetics.empty? ? nil : preload_synthetics, ) Build.ensure_program_has_entrypoint!(program, ir_program) compiled_c = CBackend.emit(ir_program, emit_line_directives:, debug_guards: debug_guards_enabled?) frontend_modules = Build.send(:frontend_modules, program) cache.store_program(key, c_source: compiled_c, frontend_modules:) source_files.each do |path, content| analysis = program.analyses_by_path[path] next unless analysis next if preload.key?(analysis.module_name.to_s) path_key = cache.module_key(path, content) fragment = modules[analysis.module_name] synths = per_module_synthetics[analysis.module_name] cache.store_module_ir(path_key, fragment, synthetics: synths) if fragment end cache.store_method_defs(method_defs_hash) update_module_caches(program, cache, source_files) [compiled_c, frontend_modules, ir_program] end |
#compile_frontend_saved_c(compiled_c, emit_line_directives) ⇒ Object
630 631 632 633 634 |
# File 'lib/milk_tea/tooling/build.rb', line 630 def compile_frontend_saved_c(compiled_c, emit_line_directives) return nil if emit_line_directives compiled_c end |
#compile_generated_c(source, compiler_flags, link_flags) ⇒ Object
753 754 755 756 757 758 759 760 761 |
# File 'lib/milk_tea/tooling/build.rb', line 753 def compile_generated_c(source, compiler_flags, link_flags) Tempfile.create(["milk-tea-build", ".c"]) do |file| file.write(source) file.flush file.close compile(file.path, compiler_flags, link_flags) end end |
#compile_or_cache_object(cache, source, pic: false) ⇒ Object
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 |
# File 'lib/milk_tea/tooling/build.rb', line 313 def compile_or_cache_object(cache, source, pic: false) return compile_to_object(source, pic:) unless cache hasher = Digest::SHA256.new hasher << source << "\0" hasher << (pic ? "pic" : "nopic") << "\0" hasher << @cc << "\0" key = hasher.hexdigest object_path = File.join(cache_cache_dir, "objects", key[0, 2], key, "object.o") return object_path if File.exist?(object_path) FileUtils.mkdir_p(File.dirname(object_path)) compile_to_object_at(source, object_path, pic:) object_path end |
#compile_to_object_at(source, output_path, pic: false) ⇒ Object
330 331 332 333 334 335 336 337 338 339 340 341 |
# File 'lib/milk_tea/tooling/build.rb', line 330 def compile_to_object_at(source, output_path, pic: false) c_path = "#{output_path}.c" File.write(c_path, source) profile_flags = profile_compiler_flags pic_flags = pic ? ["-fPIC"] : [] std_c_include_flag = "-I#{MilkTea.root.join('std/c')}" command = [@cc, "-std=c11", "-c", *pic_flags, *profile_flags, std_c_include_flag, c_path, "-o", output_path] stdout, stderr, status = Open3.capture3(*command) FileUtils.rm_f(c_path) raise BuildError, "C compilation failed:\n#{stdout}#{stderr}" unless status.success? output_path end |
#compile_wasm(c_path, compiler_flags, link_flags) ⇒ Object
900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 |
# File 'lib/milk_tea/tooling/build.rb', line 900 def compile_wasm(c_path, compiler_flags, link_flags) profile_flags = profile_compiler_flags preload_flags = wasm_asset_flags module_api_flags = ["-sINCOMING_MODULE_JS_API=canvas,print,printErr"] with_wasm_shell_file do |shell_path| command = [@cc, "-std=c11", *profile_flags, *compiler_flags, c_path, "-o", @output_path, "--shell-file", shell_path, *module_api_flags, *preload_flags, *link_flags] stdout, stderr, status = Open3.capture3(*command) return if status.success? details = [stdout, stderr].reject(&:empty?).join raise BuildError, details.empty? ? "C compiler failed" : "C compiler failed:\n#{details}" end rescue Errno::ENOENT raise BuildError, "C compiler not found: #{@cc}" end |
#compiler_available?(compiler) ⇒ Boolean
1098 1099 1100 1101 1102 1103 1104 1105 |
# File 'lib/milk_tea/tooling/build.rb', line 1098 def compiler_available?(compiler) return File.file?(compiler) && File.executable?(compiler) if compiler.include?(File::SEPARATOR) ENV.fetch("PATH", "").split(File::PATH_SEPARATOR).any? do |entry| candidate = File.join(entry, compiler) File.file?(candidate) && File.executable?(candidate) end end |
#compiler_command_name(compiler) ⇒ Object
1107 1108 1109 |
# File 'lib/milk_tea/tooling/build.rb', line 1107 def compiler_command_name(compiler) File.basename(compiler).downcase.sub(/\.(?:exe|bat|cmd|com|sh|py)\z/, "") end |
#compute_method_defs_hash(program) ⇒ Object
575 576 577 578 579 580 581 582 583 584 585 586 587 588 |
# File 'lib/milk_tea/tooling/build.rb', line 575 def compute_method_defs_hash(program) hasher = Digest::SHA256.new program.analyses_by_path.values.each do |analysis| analysis.ast.declarations.grep(AST::ExtendingBlock).each do |block| type_name = block.type_name.respond_to?(:name) ? block.type_name.name.parts.join(".") : block.type_name.to_s block.methods.each do |method| hasher << analysis.module_name.to_s << "\0" hasher << type_name << "\0" hasher << method.kind.to_s << "\0" << method.name << "\0" end end end hasher.hexdigest end |
#darwin_target? ⇒ Boolean
347 348 349 |
# File 'lib/milk_tea/tooling/build.rb', line 347 def darwin_target? @platform == :darwin || RUBY_PLATFORM =~ /darwin/ end |
#debug_guards_enabled? ⇒ Boolean
1092 1093 1094 1095 1096 |
# File 'lib/milk_tea/tooling/build.rb', line 1092 def debug_guards_enabled? return @debug_guards unless @debug_guards.nil? @profile == :debug end |
#default_package_bundle_root ⇒ Object
689 690 691 |
# File 'lib/milk_tea/tooling/build.rb', line 689 def default_package_bundle_root File.join(@project_root, "build", "dist", @platform.to_s, @profile.to_s, @package_name) end |
#default_package_output_path ⇒ Object
684 685 686 687 |
# File 'lib/milk_tea/tooling/build.rb', line 684 def default_package_output_path build_root = File.join(@project_root, "build") File.join(build_root, "bin", @platform.to_s, @profile.to_s, "#{@package_name}#{artifact_extension}") end |
#default_source_output_path(source_path) ⇒ Object
679 680 681 682 |
# File 'lib/milk_tea/tooling/build.rb', line 679 def default_source_output_path(source_path) base = source_path.sub(/\.mt\z/, "") target_wasm? ? "#{base}.html" : base end |
#detect_module_changes(program, cache, source_files) ⇒ Object
590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 |
# File 'lib/milk_tea/tooling/build.rb', line 590 def detect_module_changes(program, cache, source_files) changes = {} program.analyses_by_path.each do |path, analysis| module_name = analysis.module_name.to_s content = source_files[path] next unless content module_key = cache.module_key(path, content) state = cache.fetch_module_state(module_key) if state.nil? || state[:source_key] != module_key changes[module_name] = :changed else changes[module_name] = :unchanged end end changes end |
#emscripten_backend?(compiler) ⇒ Boolean
1111 1112 1113 |
# File 'lib/milk_tea/tooling/build.rb', line 1111 def emscripten_backend?(compiler) compiler_command_name(compiler).split(/[-_.]/).include?("emcc") end |
#ensure_compiler_available! ⇒ Object
763 764 765 766 767 |
# File 'lib/milk_tea/tooling/build.rb', line 763 def ensure_compiler_available! return if compiler_available?(@cc) raise BuildError, "C compiler not found: #{@cc}" end |
#ensure_runtime_assets_do_not_overlap_source!(source_path, staged_path) ⇒ Object
1054 1055 1056 1057 1058 1059 |
# File 'lib/milk_tea/tooling/build.rb', line 1054 def ensure_runtime_assets_do_not_overlap_source!(source_path, staged_path) return unless File.directory?(source_path) return unless path_within?(staged_path, source_path) raise BuildError, "native runtime asset output would be written inside build.assets source tree: #{staged_path}" end |
#ensure_supported_backend! ⇒ Object
769 770 771 772 773 774 775 776 777 778 779 |
# File 'lib/milk_tea/tooling/build.rb', line 769 def ensure_supported_backend! if target_wasm? return if emscripten_backend?(@cc) raise BuildError, "unsupported C compiler backend for wasm target: #{@cc}; use Emscripten emcc" end return unless msvc_style_backend?(@cc) raise BuildError, "unsupported C compiler backend for native target: #{@cc}; use a clang/gcc-style compiler driver instead of cl.exe or clang-cl" end |
#host_platform ⇒ Object
872 873 874 |
# File 'lib/milk_tea/tooling/build.rb', line 872 def host_platform MilkTea.host_platform end |
#line_directives_required? ⇒ Boolean
1088 1089 1090 |
# File 'lib/milk_tea/tooling/build.rb', line 1088 def line_directives_required? @debug || @profile == :debug end |
#load_program_with_cache(cache) ⇒ Object
466 467 468 469 470 471 472 473 474 |
# File 'lib/milk_tea/tooling/build.rb', line 466 def load_program_with_cache(cache) loader = ModuleLoader.new( module_roots: @module_roots, package_graph: @package_graph, shared_cache: cache.shared_analysis_cache, platform: @platform, ) loader.check_program(@resolved_source_path) end |
#load_wasm_shell_template_source ⇒ Object
939 940 941 942 943 |
# File 'lib/milk_tea/tooling/build.rb', line 939 def load_wasm_shell_template_source return WASM_SHELL_TEMPLATE.dup unless @html_template_path File.read(@html_template_path) end |
#msvc_style_backend?(compiler) ⇒ Boolean
1115 1116 1117 1118 |
# File 'lib/milk_tea/tooling/build.rb', line 1115 def msvc_style_backend?(compiler) tokens = compiler_command_name(compiler).split(/[-_.]/) tokens.last == "cl" || tokens.last(2) == %w[clang cl] end |
#normalize_output_path(path) ⇒ Object
719 720 721 722 723 724 725 726 727 |
# File 'lib/milk_tea/tooling/build.rb', line 719 def normalize_output_path(path) return path unless target_wasm? extension = File.extname(path) return "#{path}.html" if extension.empty? return path if extension == ".html" raise BuildError, "wasm output path must end with .html or omit the extension: #{path}" end |
#normalize_platform(value) ⇒ Object
704 705 706 707 708 709 710 711 712 713 714 715 716 717 |
# File 'lib/milk_tea/tooling/build.rb', line 704 def normalize_platform(value) case value.to_s when "", "linux" :linux when "windows", "win", "win32" :windows when "wasm", "web", "html5", "browser" :wasm when "darwin", "macos", "osx" :darwin else raise BuildError, "unknown platform #{value}; expected linux|windows|wasm|darwin" end end |
#normalize_profile(value) ⇒ Object
693 694 695 696 697 698 699 700 701 702 |
# File 'lib/milk_tea/tooling/build.rb', line 693 def normalize_profile(value) case value.to_s when "", "debug", "dev" :debug when "release", "rel" :release else raise BuildError, "unknown profile #{value}; expected debug|release" end end |
#package_manifest_required_for?(path) ⇒ Boolean
661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 |
# File 'lib/milk_tea/tooling/build.rb', line 661 def package_manifest_required_for?(path) = File.(path) return true if File.directory?() return true if File.basename() == "package.toml" current = File.dirname() loop do return true if File.file?(File.join(current, "package.toml")) parent = File.dirname(current) break if parent == current current = parent end false end |
#path_within?(path, root) ⇒ Boolean
1061 1062 1063 1064 1065 |
# File 'lib/milk_tea/tooling/build.rb', line 1061 def path_within?(path, root) normalized_path = File.(path) normalized_root = File.(root) normalized_path == normalized_root || normalized_path.start_with?(normalized_root + File::SEPARATOR) end |
#prepare_bindings(frontend_modules) ⇒ Object
735 736 737 738 739 740 741 742 743 744 745 746 |
# File 'lib/milk_tea/tooling/build.rb', line 735 def prepare_bindings(frontend_modules) frontend_modules.each do |mod| next unless mod.kind == :raw_module binding = @raw_bindings.find_by_module_name(mod.name) next unless binding binding.prepare!(cc: @cc, platform: @platform) end rescue RawBindings::Error => e raise BuildError, e. end |
#prepare_program(cache:, emit_line_directives:) ⇒ Object
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 |
# File 'lib/milk_tea/tooling/build.rb', line 279 def prepare_program(cache:, emit_line_directives:) loader = ModuleLoader.new( module_roots: @module_roots, package_graph: @package_graph, shared_cache: cache&.shared_analysis_cache, platform: @platform, ) program = loader.check_program(@resolved_source_path) # Check program-level cache for C source source_files = program.analyses_by_path.keys.each_with_object({}) { |p, h| h[p] = File.read(p, mode: "rb") } if cache key = cache.program_key(source_files: source_files.to_a) cached = cache.fetch_program(key) if cached frontend_modules = cached.frontend_modules return [source_files, program, cached.c_source, cached.c_source, frontend_modules, nil] end end ir_program = Lowering.lower(program) dbg = debug_guards_enabled? saved_c = CBackend.emit(ir_program, emit_line_directives: false, debug_guards: dbg) compiled_c = emit_line_directives ? CBackend.emit(ir_program, emit_line_directives: true, debug_guards: dbg) : saved_c frontend_modules = Build.send(:frontend_modules, program) if cache cache.store_program(key, c_source: compiled_c, frontend_modules:) end [source_files, program, compiled_c, saved_c, frontend_modules, ir_program] end |
#profile_compiler_flags ⇒ Object
1082 1083 1084 1085 1086 |
# File 'lib/milk_tea/tooling/build.rb', line 1082 def profile_compiler_flags return ["-g", "-O0"] if @debug || @profile == :debug ["-O3", "-DNDEBUG"] end |
#render_wasm_shell_template ⇒ Object
926 927 928 929 930 931 932 933 934 935 936 937 |
# File 'lib/milk_tea/tooling/build.rb', line 926 def render_wasm_shell_template template_source = load_wasm_shell_template_source validate_wasm_shell_placeholder!(template_source, WASM_SHELL_SCRIPT_PLACEHOLDER, "Emscripten {{{ SCRIPT }}}") validate_wasm_shell_placeholder!(template_source, WASM_SHELL_CANVAS_PLACEHOLDER, "Milk Tea {{{ MILK_TEA_CANVAS }}}") validate_wasm_shell_placeholder!(template_source, WASM_SHELL_OUTPUT_PLACEHOLDER, "Milk Tea {{{ MILK_TEA_OUTPUT }}}") validate_wasm_shell_placeholder!(template_source, WASM_SHELL_BOOTSTRAP_PLACEHOLDER, "Milk Tea {{{ MILK_TEA_BOOTSTRAP }}}") template_source .sub(WASM_SHELL_CANVAS_PLACEHOLDER, WASM_SHELL_CANVAS_TEMPLATE) .sub(WASM_SHELL_OUTPUT_PLACEHOLDER, WASM_SHELL_OUTPUT_TEMPLATE) .sub(WASM_SHELL_BOOTSTRAP_PLACEHOLDER, WASM_SHELL_BOOTSTRAP_TEMPLATE) end |
#resolve_compiler(requested_cc) ⇒ Object
729 730 731 732 733 |
# File 'lib/milk_tea/tooling/build.rb', line 729 def resolve_compiler(requested_cc) return ENV.fetch("EMCC", "emcc") if target_wasm? && requested_cc == ENV.fetch("CC", "cc") requested_cc end |
#reverse_import_graph(program) ⇒ Object
562 563 564 565 566 567 568 569 570 571 572 573 |
# File 'lib/milk_tea/tooling/build.rb', line 562 def reverse_import_graph(program) graph = Hash.new { |h, k| h[k] = [] } program.analyses_by_path.each do |path, analysis| module_name = analysis.module_name.to_s ast_imports = analysis.ast.respond_to?(:imports) ? analysis.ast.imports : [] ast_imports.each do |import| imported_name = import.path.to_s graph[imported_name] << module_name end end graph end |
#runtime_asset_mappings_for(target_path) ⇒ Object
1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 |
# File 'lib/milk_tea/tooling/build.rb', line 1034 def runtime_asset_mappings_for(target_path) return [] if @assets_paths.empty? return [] if target_wasm? @assets_paths.filter_map do |assets_path| staged_path = File.join(File.dirname(target_path), File.basename(assets_path)) next if staged_path == assets_path [assets_path, staged_path] end end |
#runtime_asset_pack_path_for(target_path) ⇒ Object
1050 1051 1052 |
# File 'lib/milk_tea/tooling/build.rb', line 1050 def runtime_asset_pack_path_for(target_path) File.join(File.dirname(target_path), "assets.mtpack") end |
#runtime_assets_packed? ⇒ Boolean
1046 1047 1048 |
# File 'lib/milk_tea/tooling/build.rb', line 1046 def runtime_assets_packed? @bundle && !target_wasm? && !@assets_paths.empty? end |
#sanitize_flags ⇒ Object
868 869 870 |
# File 'lib/milk_tea/tooling/build.rb', line 868 def sanitize_flags @sanitize ? %w[-fsanitize=address,undefined -fno-sanitize-recover=all -fno-omit-frame-pointer] : [] end |
#stage_runtime_assets ⇒ Object
962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 |
# File 'lib/milk_tea/tooling/build.rb', line 962 def stage_runtime_assets if runtime_assets_packed? runtime_asset_mappings_for(@output_path).each do |_source_path, staged_path| FileUtils.rm_rf(staged_path) end begin AssetPack.write(runtime_asset_pack_path_for(@output_path), @assets_paths) rescue AssetPackError => e raise BuildError, e. end return end runtime_asset_mappings_for(@output_path).each do |source_path, staged_path| ensure_runtime_assets_do_not_overlap_source!(source_path, staged_path) FileUtils.rm_rf(staged_path) FileUtils.mkdir_p(File.dirname(staged_path)) if File.directory?(source_path) FileUtils.cp_r(source_path, File.dirname(staged_path)) else FileUtils.cp(source_path, File.dirname(staged_path)) end end end |
#target_wasm? ⇒ Boolean
1071 1072 1073 |
# File 'lib/milk_tea/tooling/build.rb', line 1071 def target_wasm? @platform == :wasm end |
#target_windows? ⇒ Boolean
1067 1068 1069 |
# File 'lib/milk_tea/tooling/build.rb', line 1067 def target_windows? @platform == :windows end |
#update_module_caches(program, cache, source_files) ⇒ Object
611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 |
# File 'lib/milk_tea/tooling/build.rb', line 611 def update_module_caches(program, cache, source_files) program.analyses_by_path.each do |path, analysis| content = source_files[path] next unless content module_name = analysis.module_name.to_s module_key = cache.module_key(path, content) ast_imports = analysis.ast.respond_to?(:imports) ? analysis.ast.imports : [] deps = ast_imports.map { |import| import.path.to_s } cache.store_module_state( module_key, module_name: module_name, source_key: module_key, dependencies: deps, ) end end |
#use_package_build_for?(path) ⇒ Boolean
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 |
# File 'lib/milk_tea/tooling/build.rb', line 195 def use_package_build_for?(path) manifest = PackageManifest.load(path) return nil unless manifest&.source_path manifest_source = File.(manifest.source_path, manifest.root_dir) given_source = File.(path) return nil unless manifest_source == given_source || File.directory?(path) { manifest: } rescue PackageManifestError => e if PackageManifest.manifest_exists_for?(path) raise BuildError, e. end nil end |
#validate_bundle_mode! ⇒ Object
459 460 461 462 463 464 |
# File 'lib/milk_tea/tooling/build.rb', line 459 def validate_bundle_mode! return unless @bundle raise BuildError, "bundle mode requires a package build" unless @package_build raise BuildError, "bundle mode is supported only for native package builds" if target_wasm? end |
#validate_wasm_shell_placeholder!(template_source, placeholder, label) ⇒ Object
945 946 947 948 949 950 951 |
# File 'lib/milk_tea/tooling/build.rb', line 945 def validate_wasm_shell_placeholder!(template_source, placeholder, label) count = template_source.scan(Regexp.new(Regexp.escape(placeholder))).length return if count == 1 template_path = @html_template_path || DEFAULT_WASM_SHELL_TEMPLATE_PATH raise BuildError, "wasm HTML template must contain #{label} exactly once: #{template_path}" end |
#wasm_asset_flags ⇒ Object
953 954 955 956 957 958 959 960 |
# File 'lib/milk_tea/tooling/build.rb', line 953 def wasm_asset_flags return [] unless target_wasm? && !@assets_paths.empty? @assets_paths.flat_map do |assets_path| mount_path = "/#{File.basename(assets_path)}" ["--preload-file", "#{assets_path}@#{mount_path}"] end end |
#with_wasm_shell_file ⇒ Object
917 918 919 920 921 922 923 924 |
# File 'lib/milk_tea/tooling/build.rb', line 917 def with_wasm_shell_file Tempfile.create(["milk-tea-shell", ".html"]) do |file| file.write(render_wasm_shell_template) file.flush file.close yield file.path end end |
#write_bundle_archive ⇒ Object
990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 |
# File 'lib/milk_tea/tooling/build.rb', line 990 def write_bundle_archive return nil unless @archive archive_path = bundle_archive_path FileUtils.rm_f(archive_path) FileUtils.mkdir_p(File.dirname(archive_path)) Tempfile.create(["milk-tea-bundle", ".tar"]) do |tar_file| Gem::Package::TarWriter.new(tar_file) do |tar| add_archive_tree(tar, @bundle_root, File.basename(@bundle_root)) end tar_file.flush tar_file.rewind Zlib::GzipWriter.open(archive_path) do |gzip| IO.copy_stream(tar_file, gzip) end end archive_path end |
#write_c_file(path, source) ⇒ Object
748 749 750 751 |
# File 'lib/milk_tea/tooling/build.rb', line 748 def write_c_file(path, source) FileUtils.mkdir_p(File.dirname(path)) File.write(path, source) end |