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