@@ -548,3 +548,207 @@ task :prepare_profiling do
548548 Rake ::Task [ :"templates" ] . invoke
549549 Rake ::Task [ :"compile" ] . invoke
550550end
551+
552+ namespace :rust do
553+ namespace :rbs do
554+ RUST_DIR = File . expand_path ( "rust" , __dir__ )
555+ RBS_VERSION_FILE = File . join ( RUST_DIR , "rbs_version" )
556+
557+ VENDOR_TARGETS = {
558+ "ruby-rbs-sys" => %w[ include src ] ,
559+ "ruby-rbs" => %w[ config.yml ] ,
560+ }
561+
562+ desc "Sync vendored RBS source from the pinned version"
563+ task :sync do
564+ unless File . exist? ( RBS_VERSION_FILE )
565+ raise "#{ RBS_VERSION_FILE } not found. Run `rake rust:rbs:pin[VERSION]` first."
566+ end
567+
568+ version = File . read ( RBS_VERSION_FILE ) . strip
569+ raise "#{ RBS_VERSION_FILE } is empty" if version . empty?
570+
571+ puts "Syncing vendor/rbs/ from #{ version } ..."
572+
573+ VENDOR_TARGETS . each do |crate , entries |
574+ vendor_dir = File . join ( RUST_DIR , crate , "vendor" , "rbs" )
575+
576+ puts " Copying files for #{ crate } :"
577+ chmod_R "u+w" , vendor_dir , verbose : false if File . exist? ( vendor_dir )
578+ rm_rf vendor_dir , verbose : false
579+ mkdir_p vendor_dir , verbose : false
580+
581+ entries . each do |entry |
582+ target = File . join ( vendor_dir , entry )
583+
584+ # Extract the entry from the pinned git tag using git archive
585+ IO . popen ( [ "git" , "archive" , "--format=tar" , version , "--" , entry ] , "rb" ) do |tar |
586+ IO . popen ( [ "tar" , "xf" , "-" , "-C" , vendor_dir ] , "wb" ) do |extract |
587+ IO . copy_stream ( tar , extract )
588+ end
589+ end
590+
591+ raise "Failed to extract #{ entry } from #{ version } " unless File . exist? ( target )
592+ puts " #{ entry } "
593+ end
594+
595+ # Make files read-only to prevent accidental edits
596+ chmod_R "a-w" , vendor_dir , verbose : false
597+ end
598+
599+ puts "📦 Synced vendor/rbs/ from #{ version } (read-only)"
600+ end
601+
602+ desc "Pin a specific RBS version for Rust crates (e.g., rake rust:rbs:pin[v4.0.3])"
603+ task :pin , [ :version ] do |_t , args |
604+ version = args [ :version ] or raise "Usage: rake rust:rbs:pin[VERSION]"
605+
606+ # Verify the tag exists
607+ unless system ( "git" , "rev-parse" , "--verify" , "#{ version } ^{commit}" , out : File ::NULL , err : File ::NULL )
608+ raise "Tag #{ version } not found"
609+ end
610+
611+ File . write ( RBS_VERSION_FILE , "#{ version } \n " )
612+ puts "📌 Pinned RBS version to #{ version } "
613+
614+ Rake ::Task [ "rust:rbs:sync" ] . invoke
615+ end
616+
617+ desc "Create symlinks from vendor/rbs/ to the repository root (for development/CI)"
618+ task :symlink do
619+ VENDOR_TARGETS . each do |crate , entries |
620+ vendor_dir = File . join ( RUST_DIR , crate , "vendor" , "rbs" )
621+
622+ puts "Setting up symlinks for #{ crate } ..."
623+ entries . each do |entry |
624+ puts " #{ entry } -> repository root"
625+ end
626+
627+ chmod_R "u+w" , vendor_dir , verbose : false if File . exist? ( vendor_dir )
628+ rm_rf vendor_dir , verbose : false
629+ mkdir_p vendor_dir , verbose : false
630+
631+ entries . each do |entry |
632+ ln_s File . join ( ".." , ".." , ".." , ".." , entry ) , File . join ( vendor_dir , entry ) , verbose : false
633+ end
634+ end
635+
636+ puts "🔗 Symlinked vendor/rbs/ to repository root"
637+ end
638+ end
639+
640+ namespace :publish do
641+ def self . prepare_publish_branch ( crate_name )
642+ dry_run = ENV [ "RBS_RUST_PUBLISH_DRY_RUN" ]
643+
644+ version_file = File . join ( RUST_DIR , "rbs_version" )
645+
646+ unless File . exist? ( version_file )
647+ raise "#{ version_file } not found. Run `rake rust:rbs:pin[VERSION]` first."
648+ end
649+
650+ rbs_version = File . read ( version_file ) . strip
651+ raise "#{ version_file } is empty" if rbs_version . empty?
652+
653+ crate_version = File . read ( File . join ( RUST_DIR , crate_name , "Cargo.toml" ) ) [ /^version\s *=\s *"(.+)"/ , 1 ]
654+ release_branch = "rust/release-#{ crate_name } -#{ Time . now . strftime ( '%Y%m%d%H%M%S' ) } "
655+
656+ puts "=" * 60
657+ puts "Rust crate publish: #{ crate_name } #{ dry_run ? " (DRY RUN)" : "" } "
658+ puts "=" * 60
659+ puts " RBS source version: #{ rbs_version } "
660+ puts " #{ crate_name } : #{ crate_version } (tag: #{ crate_name } -v#{ crate_version } )"
661+ puts " Release branch: #{ release_branch } "
662+ puts "=" * 60
663+
664+ # Check that vendor dirs contain real files, not symlinks
665+ entries = VENDOR_TARGETS . fetch ( crate_name )
666+ entries . each do |entry |
667+ path = File . join ( RUST_DIR , crate_name , "vendor" , "rbs" , entry )
668+ if File . symlink? ( path )
669+ raise "#{ path } is a symlink. Run `rake rust:rbs:sync` first."
670+ end
671+ unless File . exist? ( path )
672+ raise "#{ path } does not exist. Run `rake rust:rbs:sync` first."
673+ end
674+ end
675+
676+ # Ensure working tree is clean before publishing
677+ unless `git status --porcelain` . strip . empty?
678+ raise "💢 Working tree is dirty. Please commit or stash your changes before publishing."
679+ end
680+
681+ # Create a release branch with vendor files committed
682+ original_branch = `git rev-parse --abbrev-ref HEAD` . strip
683+
684+ sh "git" , "checkout" , "-b" , release_branch , verbose : false
685+ vendor_path = File . join ( "rust" , crate_name , "vendor" , "rbs" )
686+ sh "git" , "add" , "-f" , vendor_path , verbose : false
687+ sh "git" , "commit" , "-m" , "Publish #{ crate_name } (RBS #{ rbs_version } )" , verbose : false
688+
689+ [ dry_run , crate_version , original_branch ]
690+ end
691+
692+ desc "Publish ruby-rbs-sys crate to crates.io (set RBS_RUST_PUBLISH_DRY_RUN=1 for dry-run only)"
693+ task :"ruby-rbs-sys" do
694+ crate_name = "ruby-rbs-sys"
695+ dry_run , crate_version , original_branch = prepare_publish_branch ( crate_name )
696+
697+ begin
698+ puts "🔰 Dry-run publishing..."
699+
700+ Dir . chdir ( File . join ( RUST_DIR , crate_name ) ) do
701+ sh "cargo" , "publish" , "--dry-run"
702+ end
703+
704+ puts "✅ Dry-run succeeded!"
705+
706+ unless dry_run
707+ puts "💪 Publishing #{ crate_name } for real..."
708+
709+ Dir . chdir ( File . join ( RUST_DIR , crate_name ) ) do
710+ sh "cargo" , "publish"
711+ end
712+
713+ sh "git" , "tag" , "#{ crate_name } -v#{ crate_version } "
714+ sh "git" , "push" , "origin" , "#{ crate_name } -v#{ crate_version } "
715+
716+ puts "🎉 Published #{ crate_name } successfully!"
717+ end
718+ ensure
719+ sh "git" , "checkout" , original_branch , verbose : false
720+ end
721+ end
722+
723+ desc "Publish ruby-rbs crate to crates.io (set RBS_RUST_PUBLISH_DRY_RUN=1 for dry-run only)"
724+ task :"ruby-rbs" do
725+ crate_name = "ruby-rbs"
726+ dry_run , crate_version , original_branch = prepare_publish_branch ( crate_name )
727+
728+ begin
729+ puts "🔰 Dry-run publishing..."
730+
731+ Dir . chdir ( File . join ( RUST_DIR , crate_name ) ) do
732+ sh "cargo" , "publish" , "--dry-run" , "--no-verify"
733+ end
734+
735+ puts "✅ Dry-run succeeded!"
736+
737+ unless dry_run
738+ puts "💪 Publishing #{ crate_name } for real..."
739+
740+ Dir . chdir ( File . join ( RUST_DIR , crate_name ) ) do
741+ sh "cargo" , "publish"
742+ end
743+
744+ sh "git" , "tag" , "#{ crate_name } -v#{ crate_version } "
745+ sh "git" , "push" , "origin" , "#{ crate_name } -v#{ crate_version } "
746+
747+ puts "🎉 Published #{ crate_name } successfully!"
748+ end
749+ ensure
750+ sh "git" , "checkout" , original_branch , verbose : false
751+ end
752+ end
753+ end
754+ end
0 commit comments