Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support RBS for aws-sdk-core and service gems #2961

Merged
merged 40 commits into from Jan 25, 2024

Conversation

ksss
Copy link
Contributor

@ksss ksss commented Nov 30, 2023

#2950

I have added RBS files for aws-sdk-core.
There is no impact on the actual Ruby code.
As a test for RBS, I added CI to run the rbs validate command. This test ensures that at a minimum, the RBS can be loaded.

Copy link
Contributor

@mullermp mullermp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general this looks good. Thank you! I would prefer to release this once we get the service RBS generation ready. When we're ready, we can set the minimum version here (https://github.com/aws/aws-sdk-ruby/blob/version-3/build_tools/services.rb#L13)

@@ -1,6 +1,8 @@
Unreleased Changes
------------------

* Feature - Add RBS
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we be a little more specific about what files are added?

gems/aws-sdk-core/sig/aws-sdk-core.rbs Show resolved Hide resolved
@mullermp mullermp marked this pull request as ready for review December 4, 2023 13:21
@mullermp
Copy link
Contributor

mullermp commented Dec 4, 2023

Looks like RBS fails with Jruby 9.3 and 9.4. Can you investigate?

@mullermp mullermp added pr/blocked This PR cannot be merged or reviewed, because it is blocked for some reason. pr/do-not-merge This PR should not be merged at this time. and removed pr/blocked This PR cannot be merged or reviewed, because it is blocked for some reason. labels Dec 4, 2023
@ksss
Copy link
Contributor Author

ksss commented Dec 4, 2023

Thank you for reviewing.

Even if this is released, I was dealing with the problem that it could not handle types properly due to conflicts with gem_rbs_collection.
This issue has been fixed on the RBS side, so it will be usable normally from RBS v3.4.
Under RBS v3.4, gem_rbs_collection's signature will be used in preference, but this will not be a problem for a while.
RBS v3.4 is scheduled to be included as a bundled gem in CRuby v3.3.

See also ruby/rbs#1659

Looks like RBS fails with Jruby 9.3 and 9.4. Can you investigate?

Yes, I'll investigate this issue.

@ksss
Copy link
Contributor Author

ksss commented Dec 5, 2023

RBS includes C extension.
Therefore, I have restricted the platforms for which it will be built.

@ksss
Copy link
Contributor Author

ksss commented Dec 5, 2023

@mullermp
In which code is the automatic release of the gem executed?
RBS can include documentation in itself and can be used by steep, etc.
Also, documentation can be copied from rdoc and given to the RBS.
I have prepared this copy operation as a rake task.
Is it possible to add RBS documentation generation tasks to the automatic release?

@alextwoods
Copy link
Contributor

@mullermp In which code is the automatic release of the gem executed? RBS can include documentation in itself and can be used by steep, etc. Also, documentation can be copied from rdoc and given to the RBS. I have prepared this copy operation as a rake task. Is it possible to add RBS documentation generation tasks to the automatic release?

The code for automated releases uses an internal system, but we can add this task to it.

What does including documentation in RBS do? It seems like a lot of duplicated information w/ the existing documentation?

@mullermp
Copy link
Contributor

mullermp commented Dec 5, 2023

Since our documentation is code generated, I agree that we should probably only have one location for it (source code), unless there's good reason.

@ksss
Copy link
Contributor Author

ksss commented Dec 6, 2023

@alextwoods @mullermp
The benefit of writing documentation in RBS is that, with the current behavior of Steep, for example in VSCode, when you hover over code, RBS documentation is displayed in a tooltip of vscode.
Many methods in aws-sdk-ruby have complex arguments.
Displaying the documentation on the editor can assist in coding and learning.
If the documentation is not written in RBS, only the type information is displayed.

hover with rbs documentation example on vscode:
image

Certainly, adding documentation to RBS leads to double management of documentation and complicates the build tasks. Additionally, the behavior of tools might change in the future.
So, shall we proceed without including documentation in RBS this time?

@mullermp
Copy link
Contributor

mullermp commented Dec 6, 2023

That's actually a great feature! I use RubyMine as my editor and it does not do that. I think that the code generated documentation (using docs json), the same documentation we use on client methods and params, etc, can be code generated in rbs too. Then management of this becomes easy. We can probably copy docs manually for the few hand written code.

@mullermp
Copy link
Contributor

mullermp commented Dec 6, 2023

What do you mean by documentation can be provided to rbs? I was assuming that it is just inline documentation in the same rbs file. If we are code generating these files, we can add the same documentation that's in the source code.

@ksss
Copy link
Contributor Author

ksss commented Dec 6, 2023

RBS has a feature that allows copying existing rdoc comments into RBS files.
Using this feature, after building the Ruby code, we can copy just the documentation to the RBS files with a CLI command.
Specifically, we can achieve this by running the following rake task:

rule /^rbs:doc:aws-sdk-\w+$/ do |task|
  name = task.name.split(':').last
  sh("bundle exec rdoc --ri --output tmp gems/#{name}/lib")
  sh("bundle exec rbs annotate --dir tmp gems/#{name}/sig")
end

@ksss
Copy link
Contributor Author

ksss commented Dec 6, 2023

Alternatively, we might consider adding the following to the build task:

diff --git a/tasks/build.rake b/tasks/build.rake
index 8767374fc3..06bac3aa20 100644
--- a/tasks/build.rake
+++ b/tasks/build.rake
@@ -22,6 +22,12 @@ rule /^build:aws-sdk-\w+$/ do |task|
   )
   writer = BuildTools::FileWriter.new(directory: "#{$GEMS_DIR}/#{service.gem_name}")
   writer.write_files(files)
+
+  if ENV['GEN_RBS'] && ENV['GEN_RBS'].split(',').include?(identifier)
+    name = task.name.split(':').last
+    sh("bundle exec rdoc --ri --output tmp gems/#{name}/lib")
+    sh("bundle exec rbs annotate --dir tmp gems/#{name}/sig")
+  end
 end

@mullermp
Copy link
Contributor

mullermp commented Dec 6, 2023

That makes sense! For doc generation, we use yard - will it work similarly to rdoc in this case? Whatever approach we use, I think putting logic in the build task makes the most sense. We probably don't need an ENV variable to turn it off.

I think the biggest concern we have is gem size, especially for S3. The generated documentation is very large. We may want to consider truncating it such that it links to public doc pages instead.

@ksss
Copy link
Contributor Author

ksss commented Dec 7, 2023

As of now, RBS does not have the functionality to read cache of YARD .yardoc.
Therefore, we generate temporary files for ri using by rdoc --ri command, which works fine.

When calculating the gem size, for aws-sdk-s3, the current size is 3.0MB.
Adding RBS without documentation increases it by +244K,
while adding RBS with documentation increases it by +1.8MB.

@soutaro
Copy link

soutaro commented Dec 7, 2023

@mullermp

We may want to consider truncating it such that it links to public doc pages instead.

It would be the workaround for now.

Does it help if rubygem (or bundler) has an option to delete installed RBS files after the installation? I assume the gem users will enable the option for their production builds to minimize the footprint.

@mullermp
Copy link
Contributor

mullermp commented Dec 8, 2023

I'm not sure if an option to delete installed RBS is appropriate or not.. I think the gem size increase is a concern. Could we move forward with not including docs for now and consider an enhancement to add them when we've put more thought into the correct approach and/or when yard is supported? I think at worst case, we can code generate the links to the documentation of each Client operation. i.e. insert operation name https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/ACM/Client.html#<operation name>-instance_method

@mullermp
Copy link
Contributor

mullermp commented Dec 8, 2023

I'm not sure if this is possible, but it would be nice if doc comments on RBS (or RBS tooling) could support a magic tag like @source or something that knows to load the exact comments from the source file that it is describing.

@soutaro
Copy link

soutaro commented Dec 11, 2023

I think the gem size increase is a concern.

Got it. 👍

it would be nice if doc comments on RBS (or RBS tooling) could support a magic tag like @source

It should be possible. Something like https://shopify.github.io/ruby-lsp/RubyLsp/Requests/DocumentLink.html in Ruby LSP in RBS too would help...

@ksss
Copy link
Contributor Author

ksss commented Dec 11, 2023

@soutaro
Thank you for supporting.

@mullermp @alextwoods
Based on our discussions so far, I have summarized as follows and also squashed and organized the code:

  • For gem RBS documentation, only include links like https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/ACM/Client.html#<operation name>-instance_method.
  • Divide RBS files to correspond with the structure under lib.

Copy link
Contributor Author

@ksss ksss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my environment, I have been able to generate valid RBS for the following gems.
It's okay to release aws-sdk-core in its current PR.

  • aws-sdk-s3
  • aws-sdk-sqs
  • aws-sdk-lambda
  • aws-sdk-ec2
  • aws-sdk-cloudfront
  • aws-sdk-kms
  • aws-sdk-firehose
  • aws-sdk-sns
  • aws-sdk-ssm

gems/aws-sdk-core/sig/aws-sdk-core.rbs Show resolved Hide resolved
# RBS does not support Delegator.
# the behavior is mimicked `Seahorse::Client::Response` as much as possible.
# -->
module DelegatorMethodsForResponse[DATA]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This module does not actually exist.

In RBS, it's not possible to represent the behavior of Delegator, so we can't make the return value of the operations method group Seahorse::Client::Response.
Instead, we define a non-existent module called DelegatorMethodsForResponse to represent the methods defined in Seahorse::Client::Response.
By including DelegatorMethodsForResponse in the data class, we can represent the actual behavior.

example:

module Aws
  module Lambda
    module Types
      class DestinationConfig
        include Seahorse::Client::DelegatorMethodsForResponse[DestinationConfig]

        attr_accessor on_success: OnSuccess

        attr_accessor on_failure: OnFailure
      end
    end
  end
end

Moreover, DelegatorMethodsForResponse cannot be an interface. An interface does not allow for duplicate methods. The existence of Aws::Lambda::DestinationConfig#on_success would result in a method duplication, which is why it cannot be handled as an interface.

Copy link
Contributor

@mullermp mullermp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks great! Thank you for all the hard work. :D

We should keep this PR until the service gem changes are ready. It is easier for our release system if we do them together.

@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
spec.homepage = 'https://github.com/aws/aws-sdk-ruby'
spec.license = 'Apache-2.0'
spec.require_paths = ['lib']
spec.files = Dir['LICENSE.txt', 'CHANGELOG.md', 'VERSION', 'lib/**/*.rb', 'ca-bundle.crt']
spec.files = Dir['LICENSE.txt', 'CHANGELOG.md', 'VERSION', 'lib/**/*.rb', 'ca-bundle.crt', 'sig/**/*.rbs']
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small nit - place this after lib/

@ksss ksss changed the title Add RBS for aws-sdk-core Introduce RBS generator and files for aws-sdk-core and aws-sdk-lambda Dec 12, 2023
@ksss
Copy link
Contributor Author

ksss commented Jan 18, 2024

@mullermp @alextwoods
Update completed. Shall I squash the commits previous commits?

@mullermp
Copy link
Contributor

Thank you for all of your hard work. I think this is just about ready for launch. Let's target next week. Please be advised if there are any customer issues or bugs - we will try our best to fix them, but we may need help from the RBS experts :)

@mullermp mullermp changed the title Introduce RBS generator and files for aws-sdk-core and aws-sdk-lambda RBS generator and files for aws-sdk-core and service gems Jan 18, 2024
@mullermp mullermp changed the title RBS generator and files for aws-sdk-core and service gems Support RBS for aws-sdk-core and service gems Jan 18, 2024
@ksss
Copy link
Contributor Author

ksss commented Jan 19, 2024

I'm proud to have been entrusted with such an important work 👍
Please feel free to reach out anytime. We will continue to support as much as we can.

@mullermp
Copy link
Contributor

I am typing up a blog post to be posted alongside merging this change. Could you please assist in writing a minimal case for validating rbs types when using the SDK? I tried the following:

# Steepfile
target :test do
  signature "**/*.rbs"
  check "test.rb"
end

# test.rb
# Should fail because region is 123?
client = Aws::S3::Client.new(region: 123)

but steep check succeeds.

@mullermp
Copy link
Contributor

I was also able to get type information in RubyMine but not vs code. Do you have any setup information for displaying type mismatch in vs code?

@ksss
Copy link
Contributor Author

ksss commented Jan 24, 2024

Steep

The most common way to read RBS with steep is to use rbs collection.

https://github.com/ruby/rbs/blob/master/docs/collection.md

The setup in project when using rbs collection is as follows.

$ bundle exec rbs collection init
$ bundle exec rbs collection install

This will automatically read the gem signatures described in the Gemfile.lock.
Thus, you no longer need to use signature in the Steepfile as follows.

target :test do
  check "test.rb"
end

Sample code

Unfortunately, it is currently difficult to make the following code a type error.

client = Aws::S3::Client.new(region: 123)

The main reason is that the actual aws-sdk implementation does not use the keyword argument.
Therefore, the following code must be supported.

# record pattern
client = Aws::S3::Client.new({region: 123})

# hash pattern
config = {region: 123}
client = Aws::S3::Client.new(config)

The record pattern does not currently handle optional keys (implementation is in progress).

The hash pattern only allows key and value types to be specified, so per-key checks are not possible.

Therefore, many method arguments are resolved with Hash[Symbol, untyped]. (This may be improved in the future.)

How about an example using Resource instead?

resource = Aws::S3::Resource.new
resource.buckets.each do |bucket|
  bucket.objects.limit(50).each do |obj|
    puts obj.get.etag # String
  end
end
image

@ksss
Copy link
Contributor Author

ksss commented Jan 24, 2024

As a side note, rbs v3.4 or higher is required to load gem signatures.
As gem_rbs_collection signatures will be loaded first if use rbs v3.3 or lower.

@mullermp
Copy link
Contributor

Thanks for your response on this. RubyMine correctly validates this code (and gives an incompatible types error):

client = Aws::S3::Client.new(region: 123)

It however validates the record and hash pattern as correct.

RE: Steep - I'm looking for a way to have steep check fail in some sample code. I am attempting this from the workspace (rake build aws-sdk-s3), so that is why I am using sig in the Steepfile. I do not have the aws-sdk-s3 gem in a Gemfile. I am trying to demonstrate how a customer might validate their code.

RE: VS Code - what plugins / extensions are you using to make that work?

@ksss
Copy link
Contributor Author

ksss commented Jan 25, 2024

Sounds good news! I will try RubyMine too.

For vscode user

If you want to use steep with vscode, you need to install the following extension.

https://marketplace.visualstudio.com/items?itemName=soutaro.steep-vscode

For user without Gemfile

If you do not use Gemfile, specify the library to load as follows.
(Please make sure to install gems that include sig using gem install.)

# Steepfile
target :lib do
  check "test.rb"

  library "aws-sdk-core"
  library "aws-sdk-s3"
end

For developer

If it is for development, I have prepared sample code. Please check it.

https://github.com/ksss/aws-sdk-ruby/tree/steep-sample/gems/aws-sdk-s3

$ bundle install
$ cd gems/aws-sdk-s3
$ code t.rb

@mullermp
Copy link
Contributor

Thank you for providing some help in this. I'm running into issues with the Steep plugin -

[Info  - 3:07:37 PM] Connection to server got closed. Server will restart.
[Error - 3:07:37 PM] Server initialization failed.
  Message: Pending response rejected since connection got disposed
  Code: -32097 

I'm inclined to skip this example for now :( I think the important part of the announcement is that signatures will be available.

Copy link
Contributor

@mullermp mullermp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is good to ship!

@mullermp mullermp merged commit 33e31fe into aws:version-3 Jan 25, 2024
22 of 27 checks passed
@mullermp
Copy link
Contributor

We will monitor our release tomorrow and we can do a sanity check with the published gems.

@ksss ksss deleted the rbs-aws-sdk-core branch January 26, 2024 02:17
@ksss
Copy link
Contributor Author

ksss commented Jan 26, 2024

I appreciate the support from the aws-sdk-ruby team ✨

@soutaro
Copy link

soutaro commented Jan 26, 2024

@mullermp

I'm running into issues with the Steep plugin -

Is there anything I can help you? Looks like it's an issue of ruby interpreter loading (or maybe bundle install...)
Running bundle exec steep binstub before reloading VSCode may help.

@mullermp mullermp mentioned this pull request Jan 30, 2024
2 tasks
@mullermp
Copy link
Contributor

Blog post here! https://aws.amazon.com/blogs/developer/announcing-rbs-support-for-aws-sdk-for-ruby-v3/

@ksss Thank you for your work

@soutaro Yes, I'll get back to you on trying this, currently working on something else this week.

@ksss
Copy link
Contributor Author

ksss commented Jan 31, 2024

@mullermp Nice work!
Thank you for giving me credit!

@alextwoods alextwoods removed the pr/do-not-merge This PR should not be merged at this time. label Mar 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants