Skip to content

Commit 3be497e

Browse files
committed
Extract logic to release Rails into a separate tool
1 parent 7bfe7d0 commit 3be497e

File tree

30 files changed

+823
-235
lines changed

30 files changed

+823
-235
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: Rails releaser tests
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- "tools/releaser/**"
7+
push:
8+
paths:
9+
- "tools/releaser/**"
10+
11+
permissions:
12+
contents: read
13+
14+
jobs:
15+
releaser_tests:
16+
name: releaser tests
17+
runs-on: ubuntu-latest
18+
19+
steps:
20+
- uses: actions/checkout@v4
21+
- name: Set up Ruby
22+
uses: ruby/setup-ruby@v1
23+
with:
24+
ruby-version: ruby
25+
- name: Bundle install
26+
run: bundle install
27+
working-directory: tools/releaser
28+
- run: bundle exec rake
29+
working-directory: tools/releaser

Gemfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ gem "minitest"
88
# We need a newish Rake since Active Job sets its test tasks' descriptions.
99
gem "rake", ">= 13"
1010

11+
gem "releaser", path: "tools/releaser"
12+
1113
gem "sprockets-rails", ">= 2.0.0", require: false
1214
gem "propshaft", ">= 0.1.7"
1315
gem "capybara", ">= 3.39"

Gemfile.lock

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,13 @@ PATH
121121
thor (~> 1.0, >= 1.2.2)
122122
zeitwerk (~> 2.6)
123123

124+
PATH
125+
remote: tools/releaser
126+
specs:
127+
releaser (1.0.0)
128+
minitest
129+
rake (~> 13.0)
130+
124131
GEM
125132
remote: https://rubygems.org/
126133
specs:
@@ -688,6 +695,7 @@ DEPENDENCIES
688695
redcarpet (~> 3.2.3)
689696
redis (>= 4.0.1)
690697
redis-namespace
698+
releaser!
691699
resque
692700
resque-scheduler
693701
rexml

Rakefile

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,14 @@ require "tasks/release"
77
require "railties/lib/rails/api/task"
88
require "tools/preview_docs"
99

10-
desc "Build gem files for all projects"
11-
task build: "all:build"
12-
13-
desc "Prepare the release"
14-
task prep_release: "all:prep_release"
15-
16-
desc "Release all gems to rubygems and create a tag"
17-
task release: "all:release"
18-
1910
desc "Run all tests by default"
2011
task default: %w(test test:isolated)
2112

2213
%w(test test:isolated).each do |task_name|
2314
desc "Run #{task_name} task for all projects"
2415
task task_name do
2516
errors = []
26-
FRAMEWORKS.each do |project|
17+
Releaser::FRAMEWORKS.each do |project|
2718
system(%(cd #{project} && #{$0} #{task_name} --trace)) || errors << project
2819
end
2920
fail("Errors in #{errors.join(', ')}") unless errors.empty?
@@ -32,10 +23,10 @@ end
3223

3324
desc "Smoke-test all projects"
3425
task :smoke, [:frameworks, :isolated] do |task, args|
35-
frameworks = args[:frameworks] ? args[:frameworks].split(" ") : FRAMEWORKS
26+
frameworks = args[:frameworks] ? args[:frameworks].split(" ") : Releaser::FRAMEWORKS
3627
# The arguments are positional, and users may want to specify only the isolated flag.. so we allow 'all' as a default for the first argument:
3728
if frameworks.include?("all")
38-
frameworks = FRAMEWORKS
29+
frameworks = Releaser::FRAMEWORKS
3930
end
4031

4132
isolated = args[:isolated].nil? ? true : args[:isolated] == "true"
@@ -51,9 +42,6 @@ task :smoke, [:frameworks, :isolated] do |task, args|
5142
end
5243
end
5344

54-
desc "Install gems for all projects."
55-
task install: "all:install"
56-
5745
desc "Generate documentation for the Rails framework"
5846
if ENV["EDGE"]
5947
Rails::API::EdgeTask.new("rdoc")
@@ -75,9 +63,6 @@ task :preview_docs do
7563
system("tar -czf preview.tar.gz -C preview .")
7664
end
7765

78-
desc "Bump all versions to match RAILS_VERSION"
79-
task update_versions: "all:update_versions"
80-
8166
# We have a webhook configured in GitHub that gets invoked after pushes.
8267
# This hook triggers the following tasks:
8368
#

tasks/release.rb

Lines changed: 3 additions & 215 deletions
Original file line numberDiff line numberDiff line change
@@ -1,222 +1,10 @@
11
# frozen_string_literal: true
22

3-
# Order dependent. E.g. Action Mailbox depends on Active Record so it should be after.
4-
FRAMEWORKS = %w(
5-
activesupport
6-
activemodel
7-
activerecord
8-
actionview
9-
actionpack
10-
activejob
11-
actionmailer
12-
actioncable
13-
activestorage
14-
actionmailbox
15-
actiontext
16-
railties
17-
)
18-
FRAMEWORK_NAMES = Hash.new { |h, k| k.split(/(?<=active|action)/).map(&:capitalize).join(" ") }
3+
require "releaser"
194

205
root = File.expand_path("..", __dir__)
216
version = File.read("#{root}/RAILS_VERSION").strip
22-
tag = "v#{version}"
23-
24-
directory "pkg"
25-
26-
major, minor, tiny, pre = version.split(".", 4)
27-
28-
# This "npm-ifies" the current version number
29-
# With npm, versions such as "5.0.0.rc1" or "5.0.0.beta1.1" are not compliant with its
30-
# versioning system, so they must be transformed to "5.0.0-rc1" and "5.0.0-beta1-1" respectively.
31-
# "5.0.0" --> "5.0.0"
32-
# "5.0.1" --> "5.0.100"
33-
# "5.0.0.1" --> "5.0.1"
34-
# "5.0.1.1" --> "5.0.101"
35-
# "5.0.0.rc1" --> "5.0.0-rc1"
36-
if pre
37-
pre_release = pre.match?(/rc|beta|alpha/) ? pre : nil
38-
npm_pre = pre.to_i
39-
else
40-
npm_pre = 0
41-
pre_release = nil
42-
end
43-
44-
npm_version = "#{major}.#{minor}.#{(tiny.to_i * 100) + npm_pre}#{pre_release ? "-#{pre_release}" : ""}"
45-
pre = pre ? pre.inspect : "nil"
46-
47-
(FRAMEWORKS + ["rails"]).each do |framework|
48-
namespace framework do
49-
gem = "pkg/#{framework}-#{version}.gem"
50-
gemspec = "#{framework}.gemspec"
51-
52-
task :clean do
53-
rm_f gem
54-
end
55-
56-
task :update_versions do
57-
glob = root.dup
58-
if framework == "rails"
59-
glob << "/version.rb"
60-
else
61-
glob << "/#{framework}/lib/*"
62-
glob << "/gem_version.rb"
63-
end
64-
65-
file = Dir[glob].first
66-
ruby = File.read(file)
67-
68-
ruby.gsub!(/^(\s*)MAJOR(\s*)= .*?$/, "\\1MAJOR = #{major}")
69-
raise "Could not insert MAJOR in #{file}" unless $1
70-
71-
ruby.gsub!(/^(\s*)MINOR(\s*)= .*?$/, "\\1MINOR = #{minor}")
72-
raise "Could not insert MINOR in #{file}" unless $1
73-
74-
ruby.gsub!(/^(\s*)TINY(\s*)= .*?$/, "\\1TINY = #{tiny}")
75-
raise "Could not insert TINY in #{file}" unless $1
76-
77-
ruby.gsub!(/^(\s*)PRE(\s*)= .*?$/, "\\1PRE = #{pre}")
78-
raise "Could not insert PRE in #{file}" unless $1
79-
80-
File.open(file, "w") { |f| f.write ruby }
81-
82-
require "json"
83-
if File.exist?("#{framework}/package.json") && JSON.parse(File.read("#{framework}/package.json"))["version"] != npm_version
84-
Dir.chdir("#{framework}") do
85-
if sh "which npm"
86-
sh "npm version #{npm_version} --no-git-tag-version"
87-
else
88-
raise "You must have npm installed to release Rails."
89-
end
90-
end
91-
end
92-
end
93-
94-
task gem => %w(update_versions pkg) do
95-
cmd = ""
96-
cmd += "cd #{framework} && " unless framework == "rails"
97-
cmd += "gem build #{gemspec} && mv #{framework}-#{version}.gem #{root}/pkg/"
98-
sh cmd
99-
end
100-
101-
task build: [:clean, gem]
102-
task install: :build do
103-
sh "gem install --pre #{gem}"
104-
end
105-
106-
task push: :build do
107-
otp = ""
108-
begin
109-
otp = " --otp " + `ykman oath accounts code -s rubygems.org`.chomp
110-
rescue
111-
# User doesn't have ykman
112-
end
113-
114-
sh "gem push #{gem}#{otp}"
115-
116-
if File.exist?("#{framework}/package.json")
117-
Dir.chdir("#{framework}") do
118-
npm_otp = ""
119-
begin
120-
npm_otp = " --otp " + `ykman oath accounts code -s npmjs.com`.chomp
121-
rescue
122-
# User doesn't have ykman
123-
end
124-
125-
npm_tag = ""
126-
if /[a-z]/.match?(version)
127-
npm_tag = " --tag pre"
128-
else
129-
npm_tag = " --tag latest"
130-
end
131-
132-
sh "npm publish#{npm_tag}#{npm_otp}"
133-
end
134-
end
135-
end
136-
end
137-
end
138-
139-
namespace :changelog do
140-
task :header do
141-
(FRAMEWORKS + ["guides"]).each do |fw|
142-
require "date"
143-
fname = File.join fw, "CHANGELOG.md"
144-
current_contents = File.read(fname)
145-
146-
header = "## Rails #{version} (#{Date.today.strftime('%B %d, %Y')}) ##\n\n"
147-
header += "* No changes.\n\n\n" if current_contents.start_with?("##")
148-
contents = header + current_contents
149-
File.write(fname, contents)
150-
end
151-
end
152-
153-
task :release_summary, [:base_release, :release] do |_, args|
154-
release_regexp = args[:base_release] ? Regexp.escape(args[:base_release]) : /\d+\.\d+\.\d+/
155-
156-
puts args[:release]
157-
158-
FRAMEWORKS.each do |fw|
159-
puts "## #{FRAMEWORK_NAMES[fw]}"
160-
fname = File.join fw, "CHANGELOG.md"
161-
contents = File.readlines fname
162-
contents.shift
163-
changes = []
164-
until contents.first =~ /^## Rails #{release_regexp}.*$/ ||
165-
contents.first =~ /^Please check.*for previous changes\.$/ ||
166-
contents.empty?
167-
changes << contents.shift
168-
end
169-
170-
puts changes.join
171-
puts
172-
end
173-
end
174-
end
175-
176-
namespace :all do
177-
task build: FRAMEWORKS.map { |f| "#{f}:build" } + ["rails:build"]
178-
task update_versions: FRAMEWORKS.map { |f| "#{f}:update_versions" } + ["rails:update_versions"]
179-
task install: FRAMEWORKS.map { |f| "#{f}:install" } + ["rails:install"]
180-
task push: FRAMEWORKS.map { |f| "#{f}:push" } + ["rails:push"]
181-
182-
task :ensure_clean_state do
183-
unless `git status -s | grep -v 'RAILS_VERSION\\|CHANGELOG\\|Gemfile.lock\\|package.json\\|version.rb\\|tasks/release.rb'`.strip.empty?
184-
abort "[ABORTING] `git status` reports a dirty tree. Make sure all changes are committed"
185-
end
186-
187-
unless ENV["SKIP_TAG"] || `git tag | grep '^#{tag}$'`.strip.empty?
188-
abort "[ABORTING] `git tag` shows that #{tag} already exists. Has this version already\n"\
189-
" been released? Git tagging can be skipped by setting SKIP_TAG=1"
190-
end
191-
end
192-
193-
task :bundle do
194-
sh "bundle check"
195-
end
196-
197-
task :commit do
198-
unless `git status -s`.strip.empty?
199-
File.open("pkg/commit_message.txt", "w") do |f|
200-
f.puts "# Preparing for #{version} release\n"
201-
f.puts
202-
f.puts "# UNCOMMENT THE LINE ABOVE TO APPROVE THIS COMMIT"
203-
end
204-
205-
sh "git add . && git commit --verbose --template=pkg/commit_message.txt"
206-
rm_f "pkg/commit_message.txt"
207-
end
208-
end
209-
210-
task :tag do
211-
sh "git push"
212-
sh "git tag -s -m '#{tag} release' #{tag}"
213-
sh "git push --tags"
214-
end
215-
216-
task prep_release: %w(ensure_clean_state changelog:header build bundle)
217-
218-
task release: %w(prep_release commit tag push)
219-
end
7+
releaser = Releaser.new(root, version)
2208

2219
module Announcement
22210
class Version
@@ -244,7 +32,7 @@ def rc?
24432

24533
task :announce do
24634
Dir.chdir("pkg/") do
247-
versions = ENV["VERSIONS"] ? ENV["VERSIONS"].split(",") : [ version ]
35+
versions = ENV["VERSIONS"] ? ENV["VERSIONS"].split(",") : [ releaser.version ]
24836
versions = versions.sort.map { |v| Announcement::Version.new(v) }
24937

25038
raise "Only valid for patch releases" if versions.any?(&:major_or_security?)

tasks/release_announcement_draft.erb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ To see a summary of changes, please read the release on GitHub:
1616
<%= "[#{version} CHANGELOG](https://github.com/rails/rails/releases/tag/v#{version})" %>
1717

1818
To view the changes for each gem, please read the changelogs on GitHub:
19-
<% FRAMEWORKS.sort.each do |framework| %>
20-
<%= "* [#{FRAMEWORK_NAMES[framework]} CHANGELOG](https://github.com/rails/rails/blob/v#{version}/#{framework}/CHANGELOG.md)" %>
19+
<% Releaser::FRAMEWORKS.sort.each do |framework| %>
20+
<%= "* [#{releaser.framework_name(framework)} CHANGELOG](https://github.com/rails/rails/blob/v#{version}/#{framework}/CHANGELOG.md)" %>
2121

2222
<% end %>
2323

tools/releaser/Gemfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# frozen_string_literal: true
2+
3+
source "https://rubygems.org"
4+
gemspec

tools/releaser/Gemfile.lock

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
PATH
2+
remote: .
3+
specs:
4+
releaser (1.0.0)
5+
minitest
6+
rake (~> 13.0)
7+
8+
GEM
9+
remote: https://rubygems.org/
10+
specs:
11+
minitest (5.25.1)
12+
rake (13.2.1)
13+
14+
PLATFORMS
15+
aarch64-linux
16+
ruby
17+
18+
DEPENDENCIES
19+
releaser!
20+
21+
BUNDLED WITH
22+
2.5.17

tools/releaser/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Releaser
2+
3+
Collection of helpers to release Rails.

0 commit comments

Comments
 (0)