11# frozen_string_literal: true
22
3+ # rubocop:disable Metrics/BlockLength, Lint/MissingCopEnableDirective, Style/DocumentationMethod
4+
35require 'bundler/gem_tasks'
46require 'rb_sys/cargo/metadata'
57require 'rb_sys/extensiontask'
@@ -34,57 +36,272 @@ require 'yard'
3436YARD ::Rake ::YardocTask . new
3537
3638require 'fileutils'
39+ require 'google/protobuf'
3740
3841namespace :proto do
3942 desc 'Generate API and Core protobufs'
4043 task :generate do
4144 # Remove all existing
4245 FileUtils . rm_rf ( 'lib/temporalio/api' )
4346
44- # Collect set of API protos with Google ones removed
45- api_protos = Dir . glob ( 'ext/sdk-core/sdk-core-protos/protos/api_upstream/**/*.proto' ) . reject do |proto |
46- proto . include? ( 'google' )
47- end
47+ def generate_protos ( api_protos )
48+ # Generate API to temp dir and move
49+ FileUtils . rm_rf ( 'tmp-proto' )
50+ FileUtils . mkdir_p ( 'tmp-proto' )
51+ sh 'bundle exec grpc_tools_ruby_protoc ' \
52+ '--proto_path=ext/sdk-core/sdk-core-protos/protos/api_upstream ' \
53+ '--proto_path=ext/sdk-core/sdk-core-protos/protos/api_cloud_upstream ' \
54+ '--proto_path=ext/additional_protos ' \
55+ '--ruby_out=tmp-proto ' \
56+ "#{ api_protos . join ( ' ' ) } "
57+
58+ # Walk all generated Ruby files and cleanup content and filename
59+ Dir . glob ( 'tmp-proto/temporal/api/**/*.rb' ) do |path |
60+ # Fix up the import
61+ content = File . read ( path )
62+ content . gsub! ( %r{^require 'temporal/(.*)_pb'$} , "require 'temporalio/\\ 1'" )
63+ File . write ( path , content )
64+
65+ # Remove _pb from the filename
66+ FileUtils . mv ( path , path . sub ( '_pb' , '' ) )
67+ end
4868
49- # Generate API to temp dir and move
50- FileUtils . rm_rf ( 'tmp-proto' )
51- FileUtils . mkdir_p ( 'tmp-proto' )
52- sh 'bundle exec grpc_tools_ruby_protoc ' \
53- '--proto_path=ext/sdk-core/sdk-core-protos/protos/api_upstream ' \
54- '--ruby_out=tmp-proto ' \
55- "#{ api_protos . join ( ' ' ) } "
56-
57- # Walk all generated Ruby files and cleanup content and filename
58- Dir . glob ( 'tmp-proto/temporal/api/**/*.rb' ) do |path |
59- # Fix up the import
60- content = File . read ( path )
61- content . gsub! ( %r{^require 'temporal/(.*)_pb'$} , "require 'temporalio/\\ 1'" )
62- File . write ( path , content )
63-
64- # Remove _pb from the filename
65- FileUtils . mv ( path , path . sub ( '_pb' , '' ) )
69+ # Move from temp dir and remove temp dir
70+ FileUtils . cp_r ( 'tmp-proto/temporal/api' , 'lib/temporalio' )
71+ FileUtils . rm_rf ( 'tmp-proto' )
6672 end
6773
68- # Move from temp dir and remove temp dir
69- FileUtils . mv ( 'tmp-proto/temporal/api' , 'lib/temporalio' )
70- FileUtils . rm_rf ( 'tmp-proto' )
74+ # Generate from API with Google ones removed
75+ generate_protos ( Dir . glob ( 'ext/sdk-core/sdk-core-protos/protos/api_upstream/**/*.proto' ) . reject do |proto |
76+ proto . include? ( 'google' )
77+ end )
78+
79+ # Generate from Cloud API
80+ generate_protos ( Dir . glob ( 'ext/sdk-core/sdk-core-protos/protos/api_cloud_upstream/**/*.proto' ) )
81+
82+ # Generate additional protos
83+ generate_protos ( Dir . glob ( 'ext/additional_protos/**/*.proto' ) )
7184
7285 # Write files that will help with imports. We are requiring the
7386 # request_response and not the service because the service depends on Google
7487 # API annotations we don't want to have to depend on.
75- string_lit = "# frozen_string_literal: true\n \n "
88+ File . write (
89+ 'lib/temporalio/api/cloud/cloudservice.rb' ,
90+ <<~TEXT
91+ # frozen_string_literal: true
92+
93+ require 'temporalio/api/cloud/cloudservice/v1/request_response'
94+ TEXT
95+ )
7696 File . write (
7797 'lib/temporalio/api/workflowservice.rb' ,
78- "#{ string_lit } require 'temporalio/api/workflowservice/v1/request_response'\n "
98+ <<~TEXT
99+ # frozen_string_literal: true
100+
101+ require 'temporalio/api/workflowservice/v1/request_response'
102+ TEXT
79103 )
80104 File . write (
81105 'lib/temporalio/api/operatorservice.rb' ,
82- "#{ string_lit } require 'temporalio/api/operatorservice/v1/request_response'\n "
106+ <<~TEXT
107+ # frozen_string_literal: true
108+
109+ require 'temporalio/api/operatorservice/v1/request_response'
110+ TEXT
83111 )
84112 File . write (
85113 'lib/temporalio/api.rb' ,
86- "#{ string_lit } require 'temporalio/api/operatorservice'\n require 'temporalio/api/workflowservice'\n "
114+ <<~TEXT
115+ # frozen_string_literal: true
116+
117+ require 'temporalio/api/cloud/cloudservice'
118+ require 'temporalio/api/common/v1/grpc_status'
119+ require 'temporalio/api/errordetails/v1/message'
120+ require 'temporalio/api/operatorservice'
121+ require 'temporalio/api/workflowservice'
122+
123+ module Temporalio
124+ # Raw protocol buffer models.
125+ module Api
126+ end
127+ end
128+ TEXT
87129 )
130+
131+ # Write the service classes that have the RPC calls
132+ def write_service_file ( qualified_service_name :, file_name :, class_name :, service_enum :)
133+ # Do service lookup
134+ desc = Google ::Protobuf ::DescriptorPool . generated_pool . lookup ( qualified_service_name )
135+ raise 'Failed finding service descriptor' unless desc
136+
137+ # Open file to generate Ruby code
138+ File . open ( "lib/temporalio/client/connection/#{ file_name } .rb" , 'w' ) do |file |
139+ file . puts <<~TEXT
140+ # frozen_string_literal: true
141+
142+ # Generated code. DO NOT EDIT!
143+
144+ require 'temporalio/api'
145+ require 'temporalio/client/connection/service'
146+ require 'temporalio/internal/bridge/client'
147+
148+ module Temporalio
149+ class Client
150+ class Connection
151+ # #{ class_name } API.
152+ class #{ class_name } < Service
153+ # @!visibility private
154+ def initialize(connection)
155+ super(connection, Internal::Bridge::Client::#{ service_enum } )
156+ end
157+ TEXT
158+
159+ desc . each do |method |
160+ # Camel case to snake case
161+ rpc = method . name . gsub ( /([A-Z])/ , '_\1' ) . downcase . delete_prefix ( '_' )
162+ file . puts <<-TEXT
163+
164+ # Calls #{ class_name } .#{ method . name } API call.
165+ #
166+ # @param request [#{ method . input_type . msgclass } ] API request.
167+ # @param rpc_retry [Boolean] Whether to implicitly retry known retryable errors.
168+ # @param rpc_metadata [Hash<String, String>, nil] Headers to include on the RPC call.
169+ # @param rpc_timeout [Float, nil] Number of seconds before timeout.
170+ # @return [#{ method . output_type . msgclass } ] API response.
171+ def #{ rpc } (request, rpc_retry: false, rpc_metadata: nil, rpc_timeout: nil)
172+ invoke_rpc(
173+ rpc: '#{ rpc } ',
174+ request_class: #{ method . input_type . msgclass } ,
175+ response_class: #{ method . output_type . msgclass } ,
176+ request:,
177+ rpc_retry:,
178+ rpc_metadata:,
179+ rpc_timeout:
180+ )
181+ end
182+ TEXT
183+ end
184+
185+ file . puts <<~TEXT
186+ end
187+ end
188+ end
189+ end
190+ TEXT
191+ end
192+
193+ # Open file to generate RBS code
194+ # TODO(cretz): Improve this when RBS proto is supported
195+ File . open ( "sig/temporalio/client/connection/#{ file_name } .rbs" , 'w' ) do |file |
196+ file . puts <<~TEXT
197+ # Generated code. DO NOT EDIT!
198+
199+ module Temporalio
200+ class Client
201+ class Connection
202+ class #{ class_name } < Service
203+ def initialize: (Connection) -> void
204+ TEXT
205+
206+ desc . each do |method |
207+ # Camel case to snake case
208+ rpc = method . name . gsub ( /([A-Z])/ , '_\1' ) . downcase . delete_prefix ( '_' )
209+ file . puts <<-TEXT
210+ def #{ rpc } : (untyped request, ?rpc_retry: bool, ?rpc_metadata: Hash[String, String]?, ?rpc_timeout: Float?) -> untyped
211+ TEXT
212+ end
213+
214+ file . puts <<~TEXT
215+ end
216+ end
217+ end
218+ end
219+ TEXT
220+ end
221+ end
222+
223+ require './lib/temporalio/api/workflowservice/v1/service'
224+ write_service_file (
225+ qualified_service_name : 'temporal.api.workflowservice.v1.WorkflowService' ,
226+ file_name : 'workflow_service' ,
227+ class_name : 'WorkflowService' ,
228+ service_enum : 'SERVICE_WORKFLOW'
229+ )
230+ require './lib/temporalio/api/operatorservice/v1/service'
231+ write_service_file (
232+ qualified_service_name : 'temporal.api.operatorservice.v1.OperatorService' ,
233+ file_name : 'operator_service' ,
234+ class_name : 'OperatorService' ,
235+ service_enum : 'SERVICE_OPERATOR'
236+ )
237+ require './lib/temporalio/api/cloud/cloudservice/v1/service'
238+ write_service_file (
239+ qualified_service_name : 'temporal.api.cloud.cloudservice.v1.CloudService' ,
240+ file_name : 'cloud_service' ,
241+ class_name : 'CloudService' ,
242+ service_enum : 'SERVICE_CLOUD'
243+ )
244+
245+ # Generate Rust code
246+ def generate_rust_match_arm ( file :, qualified_service_name :, service_enum :, trait :)
247+ # Do service lookup
248+ desc = Google ::Protobuf ::DescriptorPool . generated_pool . lookup ( qualified_service_name )
249+ file . puts <<~TEXT
250+ #{ service_enum } => match call.rpc.as_str() {
251+ TEXT
252+
253+ desc . to_a . sort_by ( &:name ) . each do |method |
254+ # Camel case to snake case
255+ rpc = method . name . gsub ( /([A-Z])/ , '_\1' ) . downcase . delete_prefix ( '_' )
256+ file . puts <<~TEXT
257+ "#{ rpc } " => rpc_call!(self, block, call, #{ trait } , #{ rpc } ),
258+ TEXT
259+ end
260+ file . puts <<~TEXT
261+ _ => Err(error!("Unknown RPC call {}", call.rpc)),
262+ },
263+ TEXT
264+ end
265+ File . open ( 'ext/src/client_rpc_generated.rs' , 'w' ) do |file |
266+ file . puts <<~TEXT
267+ // Generated code. DO NOT EDIT!
268+
269+ use magnus::{block::Proc, value::Opaque, Error, Ruby};
270+ use temporal_client::{CloudService, OperatorService, WorkflowService};
271+
272+ use super::{error, rpc_call};
273+ use crate::client::{Client, RpcCall, SERVICE_CLOUD, SERVICE_OPERATOR, SERVICE_WORKFLOW};
274+
275+ impl Client {
276+ pub fn invoke_rpc(&self, service: u8, block: Opaque<Proc>, call: RpcCall) -> Result<(), Error> {
277+ match service {
278+ TEXT
279+ generate_rust_match_arm (
280+ file :,
281+ qualified_service_name : 'temporal.api.workflowservice.v1.WorkflowService' ,
282+ service_enum : 'SERVICE_WORKFLOW' ,
283+ trait : 'WorkflowService'
284+ )
285+ generate_rust_match_arm (
286+ file :,
287+ qualified_service_name : 'temporal.api.operatorservice.v1.OperatorService' ,
288+ service_enum : 'SERVICE_OPERATOR' ,
289+ trait : 'OperatorService'
290+ )
291+ generate_rust_match_arm (
292+ file :,
293+ qualified_service_name : 'temporal.api.cloud.cloudservice.v1.CloudService' ,
294+ service_enum : 'SERVICE_CLOUD' ,
295+ trait : 'CloudService'
296+ )
297+ file . puts <<~TEXT
298+ _ => Err(error!("Unknown service")),
299+ }
300+ }
301+ }
302+ TEXT
303+ end
304+ sh 'cargo' , 'fmt' , '--' , 'ext/src/client_rpc_generated.rs'
88305 end
89306end
90307
@@ -104,4 +321,4 @@ Rake::Task[:build].enhance([:copy_parent_files]) do
104321 rm [ 'LICENSE' , 'README.md' ]
105322end
106323
107- task default : [ : rubocop, 'rbs:install_collection' , : steep, :compile , : test]
324+ task default : [ ' rubocop' , 'compile' , ' rbs:install_collection', ' steep' , ' test' ]
0 commit comments