11# frozen_string_literal: true
22
3+ require 'open3'
4+ require 'fileutils'
5+
36module Hrma
47 module Build
58 # A self-contained class for processing XSD documents that can run within a Ractor
69 class RactorDocumentProcessor
10+ # Keep these methods pure and Ractor-friendly
11+ # They should not use unshareable objects like Procs, Thread-locals, etc.
712 # Process a batch of XSD files
813 #
914 # @param files [Array<String>] List of XSD files to process
@@ -45,7 +50,8 @@ def self.process_single_file(file, log_dir, pwd, tool_paths)
4550 # @return [Array] Result of processing [file_path, success_flag, error_message]
4651 def self . process_with_logging ( file , log_dir , pwd , tool_paths )
4752 log_file = File . join ( log_dir , "#{ File . basename ( file , '.xsd' ) } .log" )
48- FileUtils . mkdir_p ( File . dirname ( log_file ) )
53+ # Use Dir.mkdir instead of FileUtils to avoid unshareable Procs
54+ safe_mkdir_p ( File . dirname ( log_file ) )
4955
5056 File . open ( log_file , 'w' ) do |log |
5157 log . puts "Processing #{ file } ..."
@@ -84,8 +90,9 @@ def self.process_file(file, pwd, tool_paths, log)
8490
8591 log . puts "Generating documentation for #{ file } ..."
8692
87- # Create output directory
88- create_output_directory ( paths [ :output_dir ] )
93+ # Create output directory using ractor-safe approach
94+ safe_mkdir_p ( paths [ :output_dir ] )
95+ safe_mkdir_p ( File . join ( paths [ :output_dir ] , "diagrams" ) )
8996
9097 # Generate diagrams
9198 return "Error generating diagrams" unless generate_diagrams ( file , pwd , paths [ :output_dir ] , tool_paths [ :xsdvi_path ] , log )
@@ -98,7 +105,7 @@ def self.process_file(file, pwd, tool_paths, log)
98105 return "Error generating documentation" unless generate_final_doc ( paths [ :temp_file ] , paths [ :output_file ] , file_basename , tool_paths [ :xs3p_path ] , log )
99106
100107 # Clean up and return success
101- FileUtils . rm_f ( paths [ :temp_file ] )
108+ safe_rm ( paths [ :temp_file ] )
102109 true
103110 end
104111
@@ -115,8 +122,9 @@ def self.process_file_without_logging(file, pwd, tool_paths)
115122 # Skip if up to date
116123 return true if skip_if_up_to_date? ( paths [ :output_file ] , file )
117124
118- # Create output directory
119- create_output_directory ( paths [ :output_dir ] )
125+ # Create output directory using ractor-safe approach
126+ safe_mkdir_p ( paths [ :output_dir ] )
127+ safe_mkdir_p ( File . join ( paths [ :output_dir ] , "diagrams" ) )
120128
121129 # Generate diagrams
122130 diagrams_cmd = "java -jar #{ tool_paths [ :xsdvi_path ] } #{ pwd } /#{ file } -rootNodeName all -oneNodeOnly -outputPath #{ paths [ :output_dir ] } /diagrams"
@@ -132,7 +140,7 @@ def self.process_file_without_logging(file, pwd, tool_paths)
132140 return "Error generating documentation" unless system ( xs3p_cmd )
133141
134142 # Clean up and return success
135- FileUtils . rm_f ( paths [ :temp_file ] )
143+ safe_rm ( paths [ :temp_file ] )
136144 true
137145 end
138146
@@ -165,12 +173,36 @@ def self.skip_if_up_to_date?(output_file, source_file)
165173 File . exist? ( output_file ) && File . mtime ( output_file ) > File . mtime ( source_file )
166174 end
167175
168- # Create output directory
176+ # Safe mkdir_p implementation that doesn't rely on FileUtils
177+ # This avoids the un-shareable Proc issue in Ractors
169178 #
170- # @param output_dir [String] Path to the output directory
179+ # @param dir [String] Directory path to create
171180 # @return [void]
172- def self . create_output_directory ( output_dir )
173- FileUtils . mkdir_p ( "#{ output_dir } /diagrams" )
181+ def self . safe_mkdir_p ( dir )
182+ return if Dir . exist? ( dir )
183+
184+ # Create parent directories first
185+ parent = File . dirname ( dir )
186+ unless Dir . exist? ( parent )
187+ safe_mkdir_p ( parent )
188+ end
189+
190+ # Create the directory
191+ begin
192+ Dir . mkdir ( dir )
193+ rescue Errno ::EEXIST
194+ # Directory already exists (possible race condition)
195+ end
196+ end
197+
198+ # Safe file removal that doesn't rely on FileUtils
199+ #
200+ # @param file [String] File to remove
201+ # @return [void]
202+ def self . safe_rm ( file )
203+ File . unlink ( file ) if File . exist? ( file )
204+ rescue Errno ::ENOENT
205+ # File doesn't exist, which is what we wanted anyway
174206 end
175207
176208 # Generate diagrams
@@ -184,8 +216,6 @@ def self.create_output_directory(output_dir)
184216 def self . generate_diagrams ( file , pwd , output_dir , xsdvi_path , log = nil )
185217 diagrams_cmd = "java -jar #{ xsdvi_path } #{ pwd } /#{ file } -rootNodeName all -oneNodeOnly -outputPath #{ output_dir } /diagrams"
186218
187- require 'open3'
188-
189219 if log
190220 log . puts "Running: #{ diagrams_cmd } "
191221 stdout_and_stderr_str , status = Open3 . capture2e ( diagrams_cmd )
@@ -209,8 +239,6 @@ def self.generate_diagrams(file, pwd, output_dir, xsdvi_path, log = nil)
209239 def self . generate_merged_xsd ( file , temp_file , xsdmerge_path , log = nil )
210240 xsdmerge_cmd = "xsltproc --nonet --stringparam rootxsd #{ file } --output #{ temp_file } #{ xsdmerge_path } #{ file } "
211241
212- require 'open3'
213-
214242 if log
215243 log . puts "Running: #{ xsdmerge_cmd } "
216244 stdout_and_stderr_str , status = Open3 . capture2e ( xsdmerge_cmd )
@@ -235,8 +263,6 @@ def self.generate_merged_xsd(file, temp_file, xsdmerge_path, log = nil)
235263 def self . generate_final_doc ( temp_file , output_file , file_basename , xs3p_path , log = nil )
236264 xs3p_cmd = "xsltproc --nonet --param title \" 'Schema Documentation for #{ file_basename } '\" --output #{ output_file } #{ xs3p_path } #{ temp_file } "
237265
238- require 'open3'
239-
240266 if log
241267 log . puts "Running: #{ xs3p_cmd } "
242268 stdout_and_stderr_str , status = Open3 . capture2e ( xs3p_cmd )
0 commit comments