toys-release 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +31 -0
- data/README.md +4 -4
- data/docs/guide.md +826 -1
- data/lib/toys/release/version.rb +1 -1
- data/toys/.data/templates/release-request.yml.erb +1 -1
- data/toys/.lib/toys/release/artifact_dir.rb +21 -1
- data/toys/.lib/toys/release/change_set.rb +8 -8
- data/toys/.lib/toys/release/component.rb +24 -85
- data/toys/.lib/toys/release/environment_utils.rb +29 -6
- data/toys/.lib/toys/release/performer.rb +48 -43
- data/toys/.lib/toys/release/pipeline.rb +577 -0
- data/toys/.lib/toys/release/pull_request.rb +0 -2
- data/toys/.lib/toys/release/repo_settings.rb +494 -332
- data/toys/.lib/toys/release/repository.rb +7 -8
- data/toys/.lib/toys/release/request_spec.rb +1 -1
- data/toys/.lib/toys/release/steps.rb +295 -441
- data/toys/.toys.rb +2 -2
- data/toys/_onclosed.rb +9 -1
- data/toys/gen-config.rb +137 -0
- data/toys/gen-workflows.rb +23 -6
- data/toys/perform.rb +7 -5
- data/toys/retry.rb +20 -14
- metadata +5 -6
- data/toys/.data/templates/release-hook-on-open.yml.erb +0 -30
- data/toys/_onopen.rb +0 -158
- data/toys/gen-settings.rb +0 -46
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "fileutils"
|
|
4
|
+
require "toys/utils/gems"
|
|
4
5
|
|
|
5
6
|
module Toys
|
|
6
7
|
module Release
|
|
@@ -9,294 +10,71 @@ module Toys
|
|
|
9
10
|
#
|
|
10
11
|
module Steps
|
|
11
12
|
##
|
|
12
|
-
#
|
|
13
|
+
# The interface that steps must implement.
|
|
13
14
|
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
# @param options [Hash{String=>String}] Options to pass to the step
|
|
17
|
-
# @param repository [Toys::Release::Repository]
|
|
18
|
-
# @param component [Toys::Release::Component] The component to release
|
|
19
|
-
# @param version [Gem::Version] The version to release
|
|
20
|
-
# @param artifact_dir [Toys::Release::ArtifactDir]
|
|
21
|
-
# @param dry_run [boolean] Whether to do a dry run release
|
|
22
|
-
# @param git_remote [String] The git remote to push gh-pages to
|
|
15
|
+
# This module is primarily for documentation. It need not actually be
|
|
16
|
+
# included in a step implementation.
|
|
23
17
|
#
|
|
24
|
-
|
|
25
|
-
# @return [:abort] if the pipeline should be aborted
|
|
26
|
-
#
|
|
27
|
-
def self.run(type:, name:, options:,
|
|
28
|
-
repository:, component:, version:, performer_result:,
|
|
29
|
-
artifact_dir:, dry_run:, git_remote:)
|
|
30
|
-
step_class = nil
|
|
31
|
-
begin
|
|
32
|
-
step_class = const_get(type)
|
|
33
|
-
rescue ::NameError
|
|
34
|
-
repository.utils.error("Unknown step type: #{type}")
|
|
35
|
-
return
|
|
36
|
-
end
|
|
37
|
-
step = step_class.new(repository: repository, component: component, version: version,
|
|
38
|
-
artifact_dir: artifact_dir, dry_run: dry_run, git_remote: git_remote,
|
|
39
|
-
name: name, options: options, performer_result: performer_result)
|
|
40
|
-
begin
|
|
41
|
-
step.run
|
|
42
|
-
:continue
|
|
43
|
-
rescue StepExit
|
|
44
|
-
:continue
|
|
45
|
-
rescue AbortingExit
|
|
46
|
-
:abort
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
##
|
|
51
|
-
# Internal exception signaling that the step should end immediately but
|
|
52
|
-
# the pipeline should continue.
|
|
53
|
-
# @private
|
|
54
|
-
#
|
|
55
|
-
class StepExit < ::StandardError
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
##
|
|
59
|
-
# Internal exception signaling that the step should end immediately and
|
|
60
|
-
# the pipeline should be aborted.
|
|
61
|
-
# @private
|
|
62
|
-
#
|
|
63
|
-
class AbortingExit < ::StandardError
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
##
|
|
67
|
-
# Base class for steps
|
|
68
|
-
#
|
|
69
|
-
class Base
|
|
18
|
+
module Interface
|
|
70
19
|
##
|
|
71
|
-
#
|
|
72
|
-
# @private
|
|
20
|
+
# Whether this step is a primary step (i.e. always runs.)
|
|
73
21
|
#
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
@component = component
|
|
78
|
-
@release_version = version
|
|
79
|
-
@performer_result = performer_result
|
|
80
|
-
@artifact_dir = artifact_dir
|
|
81
|
-
@dry_run = dry_run
|
|
82
|
-
@git_remote = git_remote || "origin"
|
|
83
|
-
@utils = repository.utils
|
|
84
|
-
@repo_settings = repository.settings
|
|
85
|
-
@component_settings = component.settings
|
|
86
|
-
@name = name
|
|
87
|
-
@options = options
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
##
|
|
91
|
-
# Get the option with the given key.
|
|
92
|
-
#
|
|
93
|
-
# @param key [String] Option name to fetch
|
|
94
|
-
# @param required [boolean] Whether to exit with an error if the option
|
|
95
|
-
# is not set. Defaults to false, which instead returns the default.
|
|
96
|
-
# @param default [Object] Default value to return if the option is not
|
|
97
|
-
# set and required is set to false.
|
|
22
|
+
# @param step_context [Toys::Release::Pipeline::StepContext] Context
|
|
23
|
+
# provided for the step
|
|
24
|
+
# @return [boolean]
|
|
98
25
|
#
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
def option(key, required: false, default: nil)
|
|
102
|
-
value = @options[key]
|
|
103
|
-
if !value.nil?
|
|
104
|
-
value
|
|
105
|
-
elsif required
|
|
106
|
-
exit_step("Missing option: #{key.inspect} for step #{self.class} (name = #{name.inspect})")
|
|
107
|
-
else
|
|
108
|
-
default
|
|
109
|
-
end
|
|
26
|
+
def primary?(step_context)
|
|
27
|
+
raise "Unimplemented #{step_context}"
|
|
110
28
|
end
|
|
111
29
|
|
|
112
30
|
##
|
|
113
|
-
#
|
|
114
|
-
# to the error stream.
|
|
115
|
-
# Raises an error and not return.
|
|
31
|
+
# Return the names of the standard dependencies of this step
|
|
116
32
|
#
|
|
117
|
-
# @param
|
|
118
|
-
#
|
|
119
|
-
#
|
|
33
|
+
# @param step_context [Toys::Release::Pipeline::StepContext] Context
|
|
34
|
+
# provided for the step
|
|
35
|
+
# @return [Array<String>]
|
|
120
36
|
#
|
|
121
|
-
def
|
|
122
|
-
|
|
123
|
-
if abort_pipeline
|
|
124
|
-
raise AbortingExit
|
|
125
|
-
else
|
|
126
|
-
raise StepExit
|
|
127
|
-
end
|
|
37
|
+
def dependencies(step_context)
|
|
38
|
+
raise "Unimplemented #{step_context}"
|
|
128
39
|
end
|
|
129
40
|
|
|
130
|
-
##
|
|
131
|
-
# Get the path to an artifact directory for this step.
|
|
132
|
-
#
|
|
133
|
-
# @param name [String] Optional name that can be used to point to the
|
|
134
|
-
# same directory from multiple steps. If not specified, the step
|
|
135
|
-
# name is used.
|
|
136
|
-
#
|
|
137
|
-
def artifact_dir(name = nil)
|
|
138
|
-
@artifact_dir.get(name || self.name)
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
##
|
|
142
|
-
# Run any pre-tool configured using the `"pre_tool"` option.
|
|
143
|
-
# The option value must be an array of strings representing the command.
|
|
144
|
-
#
|
|
145
|
-
def pre_tool
|
|
146
|
-
cmd = option("pre_tool")
|
|
147
|
-
return unless cmd
|
|
148
|
-
utils.log("Running pre-build tool...")
|
|
149
|
-
result = utils.exec_separate_tool(cmd, out: [:child, :err])
|
|
150
|
-
unless result.success?
|
|
151
|
-
exit_step("Pre-build tool failed: #{cmd}. Check the logs for details.")
|
|
152
|
-
end
|
|
153
|
-
utils.log("Completed pre-build tool.")
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
##
|
|
157
|
-
# Run any pre-command configured using the `"pre_command"` option.
|
|
158
|
-
# The option value must be an array of strings representing the command.
|
|
159
|
-
#
|
|
160
|
-
def pre_command
|
|
161
|
-
cmd = option("pre_command")
|
|
162
|
-
return unless cmd
|
|
163
|
-
utils.log("Running pre-build command...")
|
|
164
|
-
result = utils.exec(cmd, out: [:child, :err])
|
|
165
|
-
unless result.success?
|
|
166
|
-
exit_step("Pre-build command failed: #{cmd.inspect}. Check the logs for details.")
|
|
167
|
-
end
|
|
168
|
-
utils.log("Completed pre-build command.")
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
##
|
|
172
|
-
# Clean any files not part of the git repository, unless the `"clean"`
|
|
173
|
-
# option is explicitly set to false.
|
|
174
|
-
#
|
|
175
|
-
def pre_clean
|
|
176
|
-
return if option("clean") == false
|
|
177
|
-
count = clean_gitignored(".")
|
|
178
|
-
utils.log("Cleaned #{count} gitignored items")
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
##
|
|
182
|
-
# Check whether gh_pages is enabled for this component. If not enabled
|
|
183
|
-
# and the step requires it, exit the step.
|
|
184
|
-
#
|
|
185
|
-
# @param required [boolean] Force this step to require gh_pages. If
|
|
186
|
-
# false, the `"require_gh_pages_enabled"` option can still specify
|
|
187
|
-
# that the step requires gh_pages.
|
|
188
|
-
#
|
|
189
|
-
def check_gh_pages_enabled(required:)
|
|
190
|
-
if (required || option("require_gh_pages_enabled")) && !component_settings.gh_pages_enabled
|
|
191
|
-
utils.log("Skipping step #{name.inspect} because gh_pages is not enabled.")
|
|
192
|
-
exit_step
|
|
193
|
-
end
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
##
|
|
197
|
-
# @return [boolean] Whether this step is being run in dry run mode
|
|
198
|
-
#
|
|
199
|
-
def dry_run?
|
|
200
|
-
@dry_run
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
##
|
|
204
|
-
# @return [Toys::Release::Repository]
|
|
205
|
-
#
|
|
206
|
-
attr_reader :repository
|
|
207
|
-
|
|
208
|
-
##
|
|
209
|
-
# @return [Toys::Release::Component]
|
|
210
|
-
#
|
|
211
|
-
attr_reader :component
|
|
212
|
-
|
|
213
|
-
##
|
|
214
|
-
# @return [Toys::Release::RepoSettings]
|
|
215
|
-
#
|
|
216
|
-
attr_reader :repo_settings
|
|
217
|
-
|
|
218
|
-
##
|
|
219
|
-
# @return [Toys::Release::ComponentSettings]
|
|
220
|
-
#
|
|
221
|
-
attr_reader :component_settings
|
|
222
|
-
|
|
223
|
-
##
|
|
224
|
-
# @return [Toys::Release::EnvironmentUtils]
|
|
225
|
-
#
|
|
226
|
-
attr_reader :utils
|
|
227
|
-
|
|
228
|
-
##
|
|
229
|
-
# @return [Gem::Version]
|
|
230
|
-
#
|
|
231
|
-
attr_reader :release_version
|
|
232
|
-
|
|
233
|
-
##
|
|
234
|
-
# @return [Toys::Release::Performer::Result]
|
|
235
|
-
#
|
|
236
|
-
attr_reader :performer_result
|
|
237
|
-
|
|
238
|
-
##
|
|
239
|
-
# @return [String]
|
|
240
|
-
#
|
|
241
|
-
attr_reader :name
|
|
242
|
-
|
|
243
|
-
##
|
|
244
|
-
# @return [String]
|
|
245
|
-
#
|
|
246
|
-
attr_reader :git_remote
|
|
247
|
-
|
|
248
41
|
##
|
|
249
42
|
# Run the step.
|
|
250
|
-
# This method must be overridden in a subclass.
|
|
251
43
|
#
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
def clean_gitignored(dir)
|
|
259
|
-
count = 0
|
|
260
|
-
children = dir_children(dir)
|
|
261
|
-
result = utils.exec(["git", "check-ignore", "--stdin"], in: :controller, out: :capture) do |controller|
|
|
262
|
-
children.each { |child| controller.in.puts(child) }
|
|
263
|
-
end
|
|
264
|
-
result.captured_out.split("\n").each do |path|
|
|
265
|
-
::FileUtils.rm_rf(path)
|
|
266
|
-
utils.log("Cleaning: #{path}")
|
|
267
|
-
count += 1
|
|
268
|
-
end
|
|
269
|
-
dir_children(dir).each do |child|
|
|
270
|
-
count += clean_gitignored(child) if ::File.directory?(child)
|
|
271
|
-
end
|
|
272
|
-
count
|
|
273
|
-
end
|
|
274
|
-
|
|
275
|
-
def dir_children(dir)
|
|
276
|
-
::Dir.entries(dir)
|
|
277
|
-
.grep_v(/^\.\.?$/)
|
|
278
|
-
.sort
|
|
279
|
-
.map { |entry| ::File.join(dir, entry) }
|
|
44
|
+
# @param step_context [Toys::Release::Pipeline::StepContext] Context
|
|
45
|
+
# provided for the step
|
|
46
|
+
#
|
|
47
|
+
def run(step_context)
|
|
48
|
+
raise "Unimplemented #{step_context}"
|
|
280
49
|
end
|
|
281
50
|
end
|
|
282
51
|
|
|
52
|
+
##
|
|
53
|
+
# A step that does nothing.
|
|
54
|
+
#
|
|
55
|
+
NOOP = ::Object.new
|
|
56
|
+
|
|
283
57
|
##
|
|
284
58
|
# A step that runs a toys tool.
|
|
285
59
|
# The tool must be specified as a string array in the `"tool"` option.
|
|
286
60
|
#
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
#
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
result = utils.exec_separate_tool(tool, out: [:child, :err])
|
|
61
|
+
TOOL = ::Object.new
|
|
62
|
+
class << TOOL
|
|
63
|
+
# @private
|
|
64
|
+
def run(step_context)
|
|
65
|
+
tool = Array(step_context.option("tool", required: true))
|
|
66
|
+
chdir = step_context.option("chdir", default: ".")
|
|
67
|
+
step_context.log("Running tool #{tool.inspect}...")
|
|
68
|
+
result = step_context.utils.exec_separate_tool(tool, chdir: chdir, out: [:child, :err])
|
|
295
69
|
unless result.success?
|
|
296
|
-
|
|
297
|
-
|
|
70
|
+
if step_context.option("continue_on_error")
|
|
71
|
+
step_context.warning("Tool failed: #{tool.inspect}.")
|
|
72
|
+
step_context.exit_step
|
|
73
|
+
else
|
|
74
|
+
step_context.abort_pipeline("Tool failed: #{tool.inspect}. Check the logs for details.")
|
|
75
|
+
end
|
|
298
76
|
end
|
|
299
|
-
|
|
77
|
+
step_context.log("Completed tool")
|
|
300
78
|
end
|
|
301
79
|
end
|
|
302
80
|
|
|
@@ -305,275 +83,351 @@ module Toys
|
|
|
305
83
|
# The command must be specified as a string array in the `"command"`
|
|
306
84
|
# option.
|
|
307
85
|
#
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
#
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
result = utils.exec(command, out: [:child, :err])
|
|
86
|
+
COMMAND = ::Object.new
|
|
87
|
+
class << COMMAND
|
|
88
|
+
# @private
|
|
89
|
+
def run(step_context)
|
|
90
|
+
command = Array(step_context.option("command", required: true))
|
|
91
|
+
chdir = step_context.option("chdir", default: ".")
|
|
92
|
+
step_context.log("Running command #{command.inspect}...")
|
|
93
|
+
result = step_context.utils.exec(command, chdir: chdir, out: [:child, :err])
|
|
316
94
|
unless result.success?
|
|
317
|
-
|
|
318
|
-
|
|
95
|
+
if step_context.option("continue_on_error")
|
|
96
|
+
step_context.warning("Command failed: #{command.inspect}.")
|
|
97
|
+
step_context.exit_step
|
|
98
|
+
else
|
|
99
|
+
step_context.abort_pipeline("Command failed: #{command.inspect}. Check the logs for details.")
|
|
100
|
+
end
|
|
319
101
|
end
|
|
320
|
-
|
|
102
|
+
step_context.log("Completed command")
|
|
321
103
|
end
|
|
322
104
|
end
|
|
323
105
|
|
|
324
106
|
##
|
|
325
107
|
# A step that runs bundler
|
|
326
108
|
#
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
#
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
component.
|
|
334
|
-
|
|
109
|
+
BUNDLE = ::Object.new
|
|
110
|
+
class << BUNDLE
|
|
111
|
+
# @private
|
|
112
|
+
def run(step_context)
|
|
113
|
+
chdir = step_context.option("chdir", default: ".")
|
|
114
|
+
component = step_context.component
|
|
115
|
+
step_context.log("Running bundler for #{component.name} ...")
|
|
116
|
+
::Dir.chdir(chdir) do
|
|
117
|
+
result = component.bundle
|
|
118
|
+
unless result.success?
|
|
119
|
+
step_context.abort_pipeline("Bundle failed for #{component.name}. Check the logs for details.")
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
step_context.log("Completed bundler for #{component.name}")
|
|
123
|
+
step_context.copy_to_output(source_path: "Gemfile.lock")
|
|
335
124
|
end
|
|
336
125
|
end
|
|
337
126
|
|
|
338
127
|
##
|
|
339
|
-
# A step that builds the gem, and leaves the built gem file in
|
|
340
|
-
#
|
|
341
|
-
# pre_tool.
|
|
128
|
+
# A step that builds the gem, and leaves the built gem package file in
|
|
129
|
+
# the step's output directory.
|
|
342
130
|
#
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
#
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
131
|
+
BUILD_GEM = ::Object.new
|
|
132
|
+
class << BUILD_GEM
|
|
133
|
+
# @private
|
|
134
|
+
def run(step_context)
|
|
135
|
+
step_context.log("Building gem: #{step_context.release_description}...")
|
|
136
|
+
pkg_dir = ::File.join(step_context.output_dir, "pkg")
|
|
137
|
+
::FileUtils.mkdir_p(pkg_dir)
|
|
138
|
+
pkg_path = ::File.join(pkg_dir, step_context.gem_package_name)
|
|
139
|
+
result = step_context.utils.exec(
|
|
140
|
+
["gem", "build", "#{step_context.component.name}.gemspec", "-o", pkg_path],
|
|
141
|
+
out: [:child, :err]
|
|
142
|
+
)
|
|
354
143
|
unless result.success?
|
|
355
|
-
|
|
144
|
+
step_context.abort_pipeline("Gem build failed for #{step_context.release_description}." \
|
|
145
|
+
" Check the logs for details.")
|
|
356
146
|
end
|
|
357
|
-
|
|
358
|
-
|
|
147
|
+
step_context.log("Gem built to #{pkg_path}.")
|
|
148
|
+
step_context.log("Completed gem build.")
|
|
359
149
|
end
|
|
360
150
|
end
|
|
361
151
|
|
|
362
152
|
##
|
|
363
|
-
# A step that builds yardocs, and leaves the built documentation
|
|
364
|
-
#
|
|
365
|
-
# and/or a pre_tool.
|
|
153
|
+
# A step that builds yardocs, and leaves the built documentation in the
|
|
154
|
+
# step's output directory.
|
|
366
155
|
#
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
#
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
156
|
+
BUILD_YARD = ::Object.new
|
|
157
|
+
class << BUILD_YARD
|
|
158
|
+
# @private
|
|
159
|
+
def dependencies(step_context)
|
|
160
|
+
if step_context.option("uses_gems")
|
|
161
|
+
[]
|
|
162
|
+
else
|
|
163
|
+
[step_context.option("bundle_step", default: "bundle")]
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# @private
|
|
168
|
+
def run(step_context)
|
|
169
|
+
step_context.log("Building yard: #{step_context.release_description}...")
|
|
170
|
+
doc_dir = ::File.join(step_context.output_dir, "doc")
|
|
171
|
+
code_lines = setup_gems(step_context) + [
|
|
172
|
+
"require 'yard'",
|
|
173
|
+
"::YARD::CLI::Yardoc.run('--no-cache', '-o', '#{doc_dir}')",
|
|
174
|
+
]
|
|
175
|
+
result = step_context.utils.ruby(code_lines.join("\n"), out: [:child, :err])
|
|
176
|
+
if !result.success? || !::File.directory?(doc_dir)
|
|
177
|
+
step_context.abort_pipeline("Yard build failed for #{step_context.release_description}." \
|
|
178
|
+
" Check the logs for details.")
|
|
179
|
+
end
|
|
180
|
+
step_context.log("Docs built to #{doc_dir}.")
|
|
181
|
+
step_context.log("Completed yard build.")
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
private
|
|
185
|
+
|
|
186
|
+
def setup_gems(step_context)
|
|
187
|
+
if (uses_gems = step_context.option("uses_gems"))
|
|
188
|
+
Array(uses_gems).each do |gem_info|
|
|
189
|
+
::Toys::Utils::Gems.activate(*Array(gem_info))
|
|
190
|
+
end
|
|
191
|
+
::Toys::Utils::Gems.activate("yard")
|
|
192
|
+
[
|
|
193
|
+
"gem 'yard'",
|
|
194
|
+
"puts 'Loading gems explicitly: #{uses_gems.inspect}'",
|
|
195
|
+
]
|
|
196
|
+
else
|
|
197
|
+
step_context.copy_from_input("bundle")
|
|
198
|
+
[
|
|
199
|
+
"require 'bundler'",
|
|
200
|
+
"puts 'Running with bundler'",
|
|
201
|
+
"Bundler.ui.silence { Bundler.setup }",
|
|
202
|
+
]
|
|
382
203
|
end
|
|
383
|
-
dest_path = ::File.join(artifact_dir, "doc")
|
|
384
|
-
::FileUtils.mv("doc", dest_path)
|
|
385
|
-
utils.log("Docs built to #{dest_path}.")
|
|
386
|
-
utils.log("Completed yard build.")
|
|
387
204
|
end
|
|
388
205
|
end
|
|
389
206
|
|
|
390
207
|
##
|
|
391
|
-
# A step that releases a gem built by a previous
|
|
392
|
-
#
|
|
393
|
-
# the built gem.
|
|
208
|
+
# A step that releases a gem built by a previous step, normally a
|
|
209
|
+
# run of BUILD_GEM.
|
|
394
210
|
#
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
#
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
211
|
+
RELEASE_GEM = ::Object.new
|
|
212
|
+
class << RELEASE_GEM
|
|
213
|
+
# @private
|
|
214
|
+
def primary?(step_context)
|
|
215
|
+
::File.file?("#{step_context.component.name}.gemspec")
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# @private
|
|
219
|
+
def dependencies(step_context)
|
|
220
|
+
[source_step(step_context)]
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# @private
|
|
224
|
+
def run(step_context)
|
|
225
|
+
check_existence(step_context)
|
|
226
|
+
pkg_path = find_package(step_context)
|
|
227
|
+
if step_context.dry_run?
|
|
228
|
+
push_dry_run(step_context)
|
|
403
229
|
else
|
|
404
|
-
push_gem
|
|
230
|
+
push_gem(step_context, pkg_path)
|
|
405
231
|
end
|
|
406
232
|
end
|
|
407
233
|
|
|
408
234
|
private
|
|
409
235
|
|
|
410
|
-
def
|
|
411
|
-
|
|
412
|
-
if component.version_released?(release_version)
|
|
413
|
-
utils.warning("Gem already pushed for #{component.name} #{release_version}. Skipping.")
|
|
414
|
-
performer_result.successes << "Gem already pushed for #{component.name} #{release_version}"
|
|
415
|
-
exit_step
|
|
416
|
-
end
|
|
417
|
-
utils.log("Gem has not yet been released.")
|
|
236
|
+
def source_step(step_context)
|
|
237
|
+
step_context.option("source", default: "build_gem")
|
|
418
238
|
end
|
|
419
239
|
|
|
420
|
-
def
|
|
421
|
-
|
|
422
|
-
|
|
240
|
+
def check_existence(step_context)
|
|
241
|
+
step_context.log("Checking whether #{step_context.release_description} already exists...")
|
|
242
|
+
gem_name = step_context.component.name
|
|
243
|
+
gem_version = step_context.release_version.to_s
|
|
244
|
+
cmd = ["gem", "search", gem_name, "--exact", "--remote", "--version", gem_version]
|
|
245
|
+
if step_context.utils.capture(cmd).include?("#{gem_name} (#{gem_version})")
|
|
246
|
+
step_context.warning("Gem already pushed for #{step_context.release_description}. Skipping.")
|
|
247
|
+
step_context.add_success("Gem already pushed for #{step_context.release_description}")
|
|
248
|
+
step_context.exit_step
|
|
423
249
|
end
|
|
424
|
-
|
|
425
|
-
utils.log("DRY RUN: Gem not actually pushed to Rubygems.")
|
|
250
|
+
step_context.log("Gem has not yet been released.")
|
|
426
251
|
end
|
|
427
252
|
|
|
428
|
-
def
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
253
|
+
def find_package(step_context)
|
|
254
|
+
step_name = source_step(step_context)
|
|
255
|
+
source_dir = step_context.output_dir(step_name)
|
|
256
|
+
source_path = ::File.join(source_dir, "pkg", step_context.gem_package_name)
|
|
257
|
+
unless ::File.file?(source_path)
|
|
258
|
+
step_context.abort_pipeline("The output of step #{step_name} did not include a built gem at #{source_path}")
|
|
433
259
|
end
|
|
434
|
-
|
|
435
|
-
|
|
260
|
+
source_path
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def push_dry_run(step_context)
|
|
264
|
+
step_context.add_success("DRY RUN Rubygems push for #{step_context.release_description}.")
|
|
265
|
+
step_context.log("DRY RUN: Gem not actually pushed to Rubygems.")
|
|
436
266
|
end
|
|
437
267
|
|
|
438
|
-
def pkg_path
|
|
439
|
-
|
|
268
|
+
def push_gem(step_context, pkg_path)
|
|
269
|
+
step_context.log("Pushing gem: #{step_context.release_description}...")
|
|
270
|
+
result = step_context.utils.exec(["gem", "push", pkg_path], out: [:child, :err])
|
|
271
|
+
unless result.success?
|
|
272
|
+
step_context.abort_pipeline("Rubygems push failed for #{step_context.release_description}." \
|
|
273
|
+
" Check the logs for details.")
|
|
274
|
+
end
|
|
275
|
+
step_context.add_success("Rubygems push for #{step_context.release_description}.")
|
|
276
|
+
step_context.log("Gem push successful.")
|
|
440
277
|
end
|
|
441
278
|
end
|
|
442
279
|
|
|
443
280
|
##
|
|
444
|
-
# A step that pushes to gh-pages documentation built by a previous
|
|
445
|
-
#
|
|
446
|
-
# directory containing the built documentation.
|
|
281
|
+
# A step that pushes to gh-pages documentation built by a previous step,
|
|
282
|
+
# normally an instance of BUILD_YARD.
|
|
447
283
|
#
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
#
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
284
|
+
PUSH_GH_PAGES = ::Object.new
|
|
285
|
+
class << PUSH_GH_PAGES
|
|
286
|
+
# @private
|
|
287
|
+
def primary?(step_context)
|
|
288
|
+
step_context.component.settings.gh_pages_enabled
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# @private
|
|
292
|
+
def dependencies(step_context)
|
|
293
|
+
[source_step(step_context)]
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
# @private
|
|
297
|
+
def run(step_context)
|
|
298
|
+
gh_pages_dir = setup_gh_pages_dir(step_context)
|
|
299
|
+
component_dir = ::File.expand_path(step_context.component.settings.gh_pages_directory, gh_pages_dir)
|
|
300
|
+
dest_dir = ::File.join(component_dir, "v#{step_context.release_version}")
|
|
301
|
+
check_existence(step_context, dest_dir)
|
|
302
|
+
copy_docs_dir(step_context, dest_dir)
|
|
303
|
+
update_docs_404_page(step_context, gh_pages_dir)
|
|
304
|
+
push_docs_to_git(step_context, gh_pages_dir)
|
|
459
305
|
end
|
|
460
306
|
|
|
461
307
|
private
|
|
462
308
|
|
|
463
|
-
def
|
|
464
|
-
|
|
309
|
+
def source_step(step_context)
|
|
310
|
+
step_context.option("source", default: "build_yard")
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def setup_gh_pages_dir(step_context)
|
|
314
|
+
step_context.log("Setting up gh-pages access ...")
|
|
465
315
|
gh_token = ::ENV["GITHUB_TOKEN"]
|
|
466
|
-
|
|
467
|
-
branch: "gh-pages", remote: git_remote, dir:
|
|
316
|
+
gh_pages_dir = step_context.repository.checkout_separate_dir(
|
|
317
|
+
branch: "gh-pages", remote: step_context.git_remote, dir: step_context.temp_dir, gh_token: gh_token
|
|
468
318
|
)
|
|
469
|
-
|
|
470
|
-
|
|
319
|
+
step_context.abort_pipeline("Unable to access the gh-pages branch.") unless gh_pages_dir
|
|
320
|
+
step_context.log("Checked out gh-pages")
|
|
321
|
+
gh_pages_dir
|
|
471
322
|
end
|
|
472
323
|
|
|
473
|
-
def check_existence
|
|
324
|
+
def check_existence(step_context, dest_dir)
|
|
474
325
|
if ::File.directory?(dest_dir)
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
exit_step
|
|
326
|
+
step_context.warning("Docs already published for #{step_context.release_description}. Skipping.")
|
|
327
|
+
step_context.add_success("Docs already published for #{step_context.release_description}")
|
|
328
|
+
step_context.exit_step
|
|
478
329
|
end
|
|
479
|
-
|
|
330
|
+
step_context.log("Verified docs not yet published for #{step_context.release_description}")
|
|
480
331
|
end
|
|
481
332
|
|
|
482
|
-
def copy_docs_dir
|
|
483
|
-
|
|
484
|
-
::
|
|
485
|
-
::
|
|
333
|
+
def copy_docs_dir(step_context, dest_dir)
|
|
334
|
+
step_name = source_step(step_context)
|
|
335
|
+
source_dir = ::File.join(step_context.output_dir(step_name), "doc")
|
|
336
|
+
unless ::File.directory?(source_dir)
|
|
337
|
+
step_context.abort_pipeline("The output of step #{step_name} did not include built docs at #{source_dir}")
|
|
338
|
+
end
|
|
339
|
+
::FileUtils.mkdir_p(::File.dirname(dest_dir))
|
|
340
|
+
::FileUtils.cp_r(source_dir, dest_dir)
|
|
486
341
|
end
|
|
487
342
|
|
|
488
|
-
def update_docs_404_page
|
|
489
|
-
path = ::File.join(
|
|
343
|
+
def update_docs_404_page(step_context, gh_pages_dir)
|
|
344
|
+
path = ::File.join(gh_pages_dir, "404.html")
|
|
490
345
|
content = ::File.read(path)
|
|
491
|
-
|
|
492
|
-
|
|
346
|
+
version_var = step_context.component.settings.gh_pages_version_var
|
|
347
|
+
content.sub!(/#{version_var} = "[\w.]+";/,
|
|
348
|
+
"#{version_var} = \"#{step_context.release_version}\";")
|
|
493
349
|
::File.write(path, content)
|
|
494
350
|
end
|
|
495
351
|
|
|
496
|
-
def push_docs_to_git
|
|
497
|
-
::Dir.chdir(
|
|
498
|
-
repository.git_commit("Generated docs for #{
|
|
499
|
-
|
|
500
|
-
if dry_run?
|
|
501
|
-
|
|
502
|
-
|
|
352
|
+
def push_docs_to_git(step_context, gh_pages_dir)
|
|
353
|
+
::Dir.chdir(gh_pages_dir) do
|
|
354
|
+
step_context.repository.git_commit("Generated docs for #{step_context.release_description}",
|
|
355
|
+
signoff: step_context.repository.settings.signoff_commits?)
|
|
356
|
+
if step_context.dry_run?
|
|
357
|
+
step_context.add_success("DRY RUN documentation published for #{step_context.release_description}.")
|
|
358
|
+
step_context.log("DRY RUN: Documentation not actually published to gh-pages.")
|
|
503
359
|
else
|
|
504
|
-
result = utils.exec(["git", "push", git_remote, "gh-pages"],
|
|
360
|
+
result = step_context.utils.exec(["git", "push", step_context.git_remote, "gh-pages"],
|
|
361
|
+
out: [:child, :err])
|
|
505
362
|
unless result.success?
|
|
506
|
-
|
|
507
|
-
|
|
363
|
+
step_context.abort_pipeline("Docs publication failed for #{step_context.release_description}." \
|
|
364
|
+
" Check the logs for details.")
|
|
508
365
|
end
|
|
509
|
-
|
|
510
|
-
|
|
366
|
+
step_context.add_success("Published documentation for #{step_context.release_description}.")
|
|
367
|
+
step_context.log("Documentation publish successful.")
|
|
511
368
|
end
|
|
512
369
|
end
|
|
513
370
|
end
|
|
514
|
-
|
|
515
|
-
def component_dir
|
|
516
|
-
@component_dir ||= ::File.expand_path(component.settings.gh_pages_directory, @gh_pages_dir)
|
|
517
|
-
end
|
|
518
|
-
|
|
519
|
-
def dest_dir
|
|
520
|
-
@dest_dir ||= ::File.join(component_dir, "v#{release_version}")
|
|
521
|
-
end
|
|
522
371
|
end
|
|
523
372
|
|
|
524
373
|
##
|
|
525
374
|
# A step that creates a GitHub tag and release.
|
|
526
375
|
#
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
#
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
376
|
+
RELEASE_GITHUB = ::Object.new
|
|
377
|
+
class << RELEASE_GITHUB
|
|
378
|
+
# @private
|
|
379
|
+
def primary?(_step_context)
|
|
380
|
+
true
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
# @private
|
|
384
|
+
def run(step_context)
|
|
385
|
+
check_existence(step_context)
|
|
386
|
+
push_tag(step_context)
|
|
534
387
|
end
|
|
535
388
|
|
|
536
389
|
private
|
|
537
390
|
|
|
538
|
-
def check_existence
|
|
539
|
-
|
|
540
|
-
|
|
391
|
+
def check_existence(step_context)
|
|
392
|
+
tag_name = step_context.tag_name
|
|
393
|
+
repo_path = step_context.repository.settings.repo_path
|
|
394
|
+
step_context.log("Checking whether #{tag_name} already exists...")
|
|
395
|
+
cmd = ["gh", "api", "repos/#{repo_path}/releases/tags/#{tag_name}",
|
|
541
396
|
"-H", "Accept: application/vnd.github.v3+json"]
|
|
542
|
-
result = utils.exec(cmd, out: :null, err: :null)
|
|
397
|
+
result = step_context.utils.exec(cmd, out: :null, err: :null)
|
|
543
398
|
if result.success?
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
exit_step
|
|
399
|
+
step_context.warning("GitHub tag #{tag_name} already exists. Skipping.")
|
|
400
|
+
step_context.add_success("GitHub tag #{tag_name} already exists.")
|
|
401
|
+
step_context.exit_step
|
|
547
402
|
end
|
|
548
|
-
|
|
403
|
+
step_context.log("GitHub tag #{tag_name} has not yet been created.")
|
|
549
404
|
end
|
|
550
405
|
|
|
551
|
-
def push_tag
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
406
|
+
def push_tag(step_context)
|
|
407
|
+
tag_name = step_context.tag_name
|
|
408
|
+
repo_path = step_context.repository.settings.repo_path
|
|
409
|
+
step_context.log("Creating GitHub release #{tag_name}...")
|
|
410
|
+
changelog_file = step_context.component.changelog_file
|
|
411
|
+
changelog_content = changelog_file.read_and_verify_latest_entry(step_context.release_version)
|
|
412
|
+
release_sha = step_context.repository.current_sha
|
|
555
413
|
body = ::JSON.dump(tag_name: tag_name,
|
|
556
414
|
target_commitish: release_sha,
|
|
557
|
-
name:
|
|
415
|
+
name: step_context.release_description,
|
|
558
416
|
body: changelog_content)
|
|
559
|
-
if dry_run?
|
|
560
|
-
|
|
561
|
-
|
|
417
|
+
if step_context.dry_run?
|
|
418
|
+
step_context.add_success("DRY RUN GitHub tag #{tag_name}.")
|
|
419
|
+
step_context.log("DRY RUN: GitHub tag #{tag_name} not actually created.")
|
|
562
420
|
else
|
|
563
|
-
cmd = ["gh", "api", "repos/#{
|
|
421
|
+
cmd = ["gh", "api", "repos/#{repo_path}/releases", "--input", "-",
|
|
564
422
|
"-H", "Accept: application/vnd.github.v3+json"]
|
|
565
|
-
result = utils.exec(cmd, in: [:string, body], out: :null)
|
|
423
|
+
result = step_context.utils.exec(cmd, in: [:string, body], out: :null)
|
|
566
424
|
unless result.success?
|
|
567
|
-
|
|
425
|
+
step_context.abort_pipeline("Unable to create release #{tag_name}. Check the logs for details.")
|
|
568
426
|
end
|
|
569
|
-
|
|
570
|
-
|
|
427
|
+
step_context.add_success("Created release with tag #{tag_name} on GitHub.")
|
|
428
|
+
step_context.log("GitHub release successful.")
|
|
571
429
|
end
|
|
572
430
|
end
|
|
573
|
-
|
|
574
|
-
def tag_name
|
|
575
|
-
"#{component.name}/v#{release_version}"
|
|
576
|
-
end
|
|
577
431
|
end
|
|
578
432
|
end
|
|
579
433
|
end
|