buildkite-builder 2.4.0 → 3.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4c4c2623f11f578f2839830abd97f02de9189aca48838b51af749a2ed63b5d43
4
- data.tar.gz: 80ad9118885ca08b62b9d849f5bde06d60ab8ed13095921a4bf017c1b23754f1
3
+ metadata.gz: bef719c816c5d64756bbcd041ca2a3fabe8fe59bff47cc154948207ce64a672f
4
+ data.tar.gz: f25762b1422e540f395526df9d6082e1d5908ea0b9b290bf025a365b56c317ff
5
5
  SHA512:
6
- metadata.gz: faf4b6d95c5ce06432dec04ddc53df0b6ca4a0e8e0986fe5798b02678513847f1bc1dc6cfa4e3a81d603d0aad5db55e6b7f7ece5f21801fb92e52ae8e6895d68
7
- data.tar.gz: 8c134596ebac502f43c0da347dbe36f5db527534b50781623cd3ea0b2e69a8ff6ecb9bfc1cd9ec697b616bcfe7c3804bc728a9c6a70d5c71aecfc4d772fa9931
6
+ metadata.gz: c00f3fba91b34cf907c99ff839612270ef8aecb754c988af52226e979d5e743a508963d1d06d9957b529a2e3ba47cf6ba0951ec33290b8581dc9937b4178033a
7
+ data.tar.gz: 6e62bff8ecdb50b72bdb3afb8fd6fa1385f17ef1852b607f5273570cab1d396ee65d8908a78604d48ccb7a564ad4cb0f9dbff6b94b7a016cfd9ee881e1d4098a
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## 3.1.0
2
+ * Add subpipeline support to save triggered pipeline's YML definition beforehand to artifacts and pass down the file to an ENV for pipeline setup.
3
+
4
+ ## 3.0.0
5
+ * Remove manifest features to prevent Github API dependency and simplify the gem to focus on Buildkite features.
6
+
7
+ ## 2.4.1
8
+ * Fix pipeline upload as artifact logic.
9
+
1
10
  ## 2.4.0
2
11
  * Upload custom pipeline artifacts in a single command.
3
12
  * Only upload the pipeline as an artifact when the pipeline upload fails.
data/README.md CHANGED
@@ -48,7 +48,7 @@ At its core, BKB is really just a YAML builder. This tool allows you to scale yo
48
48
  - Perform pre-build code/diff analysis to determine whether or not to to add a step to the pipeline.
49
49
  - Reorder pipeline steps dynamically.
50
50
  - Augment your pipeline steps with BKB processors.
51
-
51
+
52
52
  ### Pipeline Files
53
53
 
54
54
  Your repo can contain as many pipeline definitions as you'd like. By convention, pipeline file structure are as such:
@@ -74,9 +74,9 @@ Buildkite::Builder.pipeline do
74
74
  label "Rspec", emoji: :rspec
75
75
  command "bundle exec rspec"
76
76
  end
77
-
77
+
78
78
  wait
79
-
79
+
80
80
  trigger do
81
81
  trigger "deploy-pipeline"
82
82
  end
@@ -127,7 +127,7 @@ You can then include the template into the the pipeline once or as many time as
127
127
  ```ruby
128
128
  Buildkite::Builder.pipeline do
129
129
  command(:rspec)
130
-
130
+
131
131
  # Reuse and agument templates on the fly.
132
132
  command(:rspec) do
133
133
  label "Run RSpec again!"
@@ -135,6 +135,36 @@ Buildkite::Builder.pipeline do
135
135
  end
136
136
  ```
137
137
 
138
+ ### Subpipeline
139
+
140
+ While triggering another pipeline, you can predefine subpipeline's steps using `pipeline(NAME_OF_SUBPIPELINE)` in the main pipeline's `pipeline.rb` file and share with main pipeline's plugins and templates definition.
141
+
142
+ `.buildkite/pipelines/pipeline-triggerer/pipeline.rb`
143
+
144
+ ```ruby
145
+ Buildkite::Builder.pipeline do
146
+ pipeline('rspec-pipeline') do
147
+ command do
148
+ label "Run RSpec in separate pipeline"
149
+ end
150
+ end
151
+ end
152
+ ```
153
+
154
+ Inside your Buildkite pipeline setup, you can do the following:
155
+
156
+ In `https://buildkite.com/your-org/rspec-pipeline/steps`
157
+
158
+ ```yaml
159
+ steps:
160
+ - label: ":pipeline:"
161
+ commands:
162
+ - buildkite-agent artifact download $BKB_SUBPIPELINE_FILE . --build $BUILDKITE_TRIGGERED_FROM_BUILD_ID
163
+ - buildkite-agent pipeline upload $BKB_SUBPIPELINE_FILE
164
+ ```
165
+
166
+ This will upload the pregenerated `pipeline.yml` to `rspec-pipeline`.
167
+
138
168
  ## Development
139
169
 
140
170
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.4.0
1
+ 3.1.0
@@ -5,7 +5,6 @@ module Buildkite
5
5
  module Commands
6
6
  using Rainbow
7
7
  COMMANDS = {
8
- 'files' => :Files,
9
8
  'preview' => :Preview,
10
9
  'run' => :Run
11
10
  }.freeze
@@ -9,7 +9,7 @@ module Buildkite
9
9
  @data.each_with_object({}) do |(key, value), hash|
10
10
  value = value.respond_to?(:to_definition) ? value.to_definition : value
11
11
 
12
- next if value.empty?
12
+ next if value.nil? || value.empty?
13
13
 
14
14
  hash[key] = value
15
15
  end
@@ -0,0 +1,96 @@
1
+ require 'securerandom'
2
+
3
+ module Buildkite
4
+ module Builder
5
+ module Extensions
6
+ class SubPipelines < Extension
7
+ class Pipeline
8
+ include Buildkite::Pipelines::Attributes
9
+
10
+ attr_reader :data, :name
11
+
12
+ attribute :depends_on, append: true
13
+ attribute :key
14
+
15
+ def self.to_sym
16
+ name.split('::').last.downcase.to_sym
17
+ end
18
+
19
+ def initialize(name, steps, &block)
20
+ @name = name
21
+ @data = Data.new
22
+ @data.steps = StepCollection.new(
23
+ steps.templates,
24
+ steps.plugins
25
+ )
26
+ @data.notify = []
27
+ @data.env = {}
28
+
29
+ @dsl = Dsl.new(self)
30
+ @dsl.extend(Extensions::Steps)
31
+ @dsl.extend(Extensions::Notify)
32
+ @dsl.extend(Extensions::Env)
33
+ instance_eval(&block) if block_given?
34
+ self
35
+ end
36
+
37
+ def to_h
38
+ attributes = super
39
+ attributes.merge(data.to_definition)
40
+ end
41
+
42
+ def method_missing(method_name, *args, **kwargs, &_block)
43
+ @dsl.public_send(method_name, *args, **kwargs, &_block)
44
+ end
45
+
46
+ def pipeline_yml
47
+ @pipeline_yml ||= "tmp/buildkite-builder/#{SecureRandom.urlsafe_base64}.yml"
48
+ end
49
+ end
50
+
51
+ def prepare
52
+ context.data.pipelines = PipelineCollection.new(context.artifacts)
53
+ end
54
+
55
+ dsl do
56
+ def pipeline(name, template = nil, &block)
57
+ raise "Subpipeline must have a name" if name.empty?
58
+ raise "Subpipeline does not allow nested in another Subpipeline" if context.is_a?(Buildkite::Builder::Extensions::SubPipelines::Pipeline)
59
+ sub_pipeline = Buildkite::Builder::Extensions::SubPipelines::Pipeline.new(name, context.data.steps, &block)
60
+
61
+ context.data.pipelines.add(sub_pipeline)
62
+
63
+ if template
64
+ # Use predefined template
65
+ step = context.data.steps.add(Pipelines::Steps::Trigger, template)
66
+
67
+ if step.build.nil?
68
+ step.build(env: { BKB_SUBPIPELINE_FILE: sub_pipeline.pipeline_yml })
69
+ else
70
+ step.build[:env].merge!(BKB_SUBPIPELINE_FILE: sub_pipeline.pipeline_yml)
71
+ end
72
+ else
73
+ # Generic trigger step
74
+ context.data.steps.add(Pipelines::Steps::Trigger, key: "subpipeline_#{name}_#{context.data.pipelines.count}") do |context|
75
+ key context[:key]
76
+ label name.capitalize
77
+ trigger name
78
+ build(
79
+ message: '${BUILDKITE_MESSAGE}',
80
+ commit: '${BUILDKITE_COMMIT}',
81
+ branch: '${BUILDKITE_BRANCH}',
82
+ env: {
83
+ BUILDKITE_PULL_REQUEST: '${BUILDKITE_PULL_REQUEST}',
84
+ BUILDKITE_PULL_REQUEST_BASE_BRANCH: '${BUILDKITE_PULL_REQUEST_BASE_BRANCH}',
85
+ BUILDKITE_PULL_REQUEST_REPO: '${BUILDKITE_PULL_REQUEST_REPO}',
86
+ BKB_SUBPIPELINE_FILE: sub_pipeline.pipeline_yml
87
+ }
88
+ )
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -6,6 +6,7 @@ module Buildkite
6
6
  autoload :Env, File.expand_path('extensions/env', __dir__)
7
7
  autoload :Lib, File.expand_path('extensions/lib', __dir__)
8
8
  autoload :Notify, File.expand_path('extensions/notify', __dir__)
9
+ autoload :SubPipelines, File.expand_path('extensions/sub_pipelines', __dir__)
9
10
  autoload :Steps, File.expand_path('extensions/steps', __dir__)
10
11
  autoload :Use, File.expand_path('extensions/use', __dir__)
11
12
  end
@@ -4,7 +4,6 @@ module Buildkite
4
4
  module Builder
5
5
  module Loaders
6
6
  autoload :Abstract, File.expand_path('loaders/abstract', __dir__)
7
- autoload :Manifests, File.expand_path('loaders/manifests', __dir__)
8
7
  autoload :Templates, File.expand_path('loaders/templates', __dir__)
9
8
  autoload :Extensions, File.expand_path('loaders/extensions', __dir__)
10
9
  end
@@ -37,7 +37,7 @@ module Buildkite
37
37
  use(Extensions::Env)
38
38
  use(Extensions::Notify)
39
39
  use(Extensions::Steps)
40
- load_manifests
40
+ use(Extensions::SubPipelines)
41
41
  end
42
42
 
43
43
  def upload
@@ -52,9 +52,10 @@ module Buildkite
52
52
  file.write(contents)
53
53
 
54
54
  logger.info "+++ :pipeline: Uploading pipeline"
55
- unless Buildkite::Pipelines::Command.pipeline!(:upload, file.path)
55
+ unless Buildkite::Pipelines::Command.pipeline(:upload, file.path)
56
56
  logger.info "Pipeline upload failed, saving as artifact…"
57
57
  Buildkite::Pipelines::Command.artifact!(:upload, file.path)
58
+ abort
58
59
  end
59
60
  logger.info "+++ :toolbox: Setting job meta-data to #{Buildkite.env.job_id.color(:yellow)}"
60
61
  Buildkite::Pipelines::Command.meta_data!(:set, Builder::META_DATA.fetch(:job), Buildkite.env.job_id)
@@ -81,12 +82,6 @@ module Buildkite
81
82
 
82
83
  attr_reader :extensions
83
84
 
84
- def load_manifests
85
- Loaders::Manifests.load(root).each do |name, asset|
86
- Manifest[name] = asset
87
- end
88
- end
89
-
90
85
  def upload_artifacts
91
86
  return if artifacts.empty?
92
87
 
@@ -0,0 +1,39 @@
1
+ require "forwardable"
2
+
3
+ module Buildkite
4
+ module Builder
5
+ class PipelineCollection
6
+ extend Forwardable
7
+
8
+ attr_reader :pipelines
9
+
10
+ def_delegator :@pipelines, :count
11
+
12
+ def initialize(artifacts)
13
+ @artifacts = artifacts
14
+ @pipelines = []
15
+ end
16
+
17
+ def add(pipeline)
18
+ unless pipeline.is_a?(Buildkite::Builder::Extensions::SubPipelines::Pipeline)
19
+ raise "`#{pipeline}` must be a Buildkite::Builder::Extensions::SubPipelines::Pipeline"
20
+ end
21
+
22
+ pipelines << pipeline
23
+ end
24
+
25
+ def to_definition
26
+ # Instead of generates pipeline.yml, subpipelines save generated file to artifacts
27
+ pipelines.each do |pipeline|
28
+ file = Pathname.new(pipeline.pipeline_yml)
29
+ file.dirname.mkpath
30
+ file.write(YAML.dump(Pipelines::Helpers.sanitize(pipeline.to_h)))
31
+
32
+ @artifacts << file
33
+ end
34
+
35
+ nil
36
+ end
37
+ end
38
+ end
39
+ end
@@ -13,16 +13,14 @@ module Buildkite
13
13
  autoload :Extension, File.expand_path('builder/extension', __dir__)
14
14
  autoload :ExtensionManager, File.expand_path('builder/extension_manager', __dir__)
15
15
  autoload :Extensions, File.expand_path('builder/extensions', __dir__)
16
- autoload :FileResolver, File.expand_path('builder/file_resolver', __dir__)
17
- autoload :Github, File.expand_path('builder/github', __dir__)
18
16
  autoload :Loaders, File.expand_path('builder/loaders', __dir__)
19
17
  autoload :LoggingUtils, File.expand_path('builder/logging_utils', __dir__)
20
- autoload :Manifest, File.expand_path('builder/manifest', __dir__)
21
18
  autoload :Processors, File.expand_path('builder/processors', __dir__)
22
19
  autoload :Rainbow, File.expand_path('builder/rainbow', __dir__)
23
20
  autoload :Plugin, File.expand_path('builder/plugin', __dir__)
24
21
  autoload :PluginCollection, File.expand_path('builder/plugin_collection', __dir__)
25
22
  autoload :StepCollection, File.expand_path('builder/step_collection', __dir__)
23
+ autoload :PipelineCollection, File.expand_path('builder/pipeline_collection', __dir__)
26
24
  autoload :TemplateManager, File.expand_path('builder/template_manager', __dir__)
27
25
  autoload :PluginManager, File.expand_path('builder/plugin_manager', __dir__)
28
26
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: buildkite-builder
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.0
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ngan Pham
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2022-01-08 00:00:00.000000000 Z
12
+ date: 2022-06-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rainbow
@@ -116,7 +116,6 @@ files:
116
116
  - lib/buildkite/builder.rb
117
117
  - lib/buildkite/builder/commands.rb
118
118
  - lib/buildkite/builder/commands/abstract.rb
119
- - lib/buildkite/builder/commands/files.rb
120
119
  - lib/buildkite/builder/commands/preview.rb
121
120
  - lib/buildkite/builder/commands/run.rb
122
121
  - lib/buildkite/builder/data.rb
@@ -129,19 +128,16 @@ files:
129
128
  - lib/buildkite/builder/extensions/lib.rb
130
129
  - lib/buildkite/builder/extensions/notify.rb
131
130
  - lib/buildkite/builder/extensions/steps.rb
131
+ - lib/buildkite/builder/extensions/sub_pipelines.rb
132
132
  - lib/buildkite/builder/extensions/use.rb
133
- - lib/buildkite/builder/file_resolver.rb
134
- - lib/buildkite/builder/github.rb
135
133
  - lib/buildkite/builder/group.rb
136
134
  - lib/buildkite/builder/loaders.rb
137
135
  - lib/buildkite/builder/loaders/abstract.rb
138
136
  - lib/buildkite/builder/loaders/extensions.rb
139
- - lib/buildkite/builder/loaders/manifests.rb
140
137
  - lib/buildkite/builder/loaders/templates.rb
141
138
  - lib/buildkite/builder/logging_utils.rb
142
- - lib/buildkite/builder/manifest.rb
143
- - lib/buildkite/builder/manifest/rule.rb
144
139
  - lib/buildkite/builder/pipeline.rb
140
+ - lib/buildkite/builder/pipeline_collection.rb
145
141
  - lib/buildkite/builder/plugin.rb
146
142
  - lib/buildkite/builder/plugin_collection.rb
147
143
  - lib/buildkite/builder/plugin_manager.rb
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Buildkite
4
- module Builder
5
- module Commands
6
- class Files < Abstract
7
- private
8
-
9
- self.description = 'Outputs files that match the specified manifest.'
10
-
11
- def run
12
- manifests = Loaders::Manifests.load(pipeline_path)
13
- puts manifests[options[:manifest]].files.sort.join("\n")
14
- end
15
-
16
- def parse_options(opts)
17
- opts.on('--manifest MANIFEST', 'The manifest to use') do |manifest|
18
- options[:manifest] = manifest
19
- end
20
- end
21
- end
22
- end
23
- end
24
- end
@@ -1,59 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'open3'
4
- require 'set'
5
-
6
- module Buildkite
7
- module Builder
8
- class FileResolver
9
- @cache = true
10
-
11
- attr_reader :modified_files
12
-
13
- class << self
14
- attr_accessor :cache
15
-
16
- def resolve(reset = false)
17
- @resolve = nil if !cache || reset
18
- @resolve ||= new
19
- end
20
- end
21
-
22
- def initialize
23
- @modified_files = Set.new(pull_request? ? files_from_pull_request.sort! : files_from_git.sort!)
24
- end
25
-
26
- private
27
-
28
- def files_from_pull_request
29
- Github.pull_request_files.map { |f| f.fetch('filename') }
30
- end
31
-
32
- def files_from_git
33
- if Buildkite.env
34
- changed_files = command("git diff-tree --no-commit-id --name-only -r #{Buildkite.env.commit}")
35
- else
36
- default_branch = command('git symbolic-ref refs/remotes/origin/HEAD').strip
37
- changed_files = command("git diff --name-only #{default_branch}")
38
- changed_files << command('git diff --name-only')
39
- end
40
-
41
- changed_files.split.uniq.sort
42
- end
43
-
44
- def pull_request?
45
- Buildkite.env&.pull_request
46
- end
47
-
48
- def command(cmd)
49
- output, status = Open3.capture2(*cmd.split)
50
-
51
- if status.success?
52
- output
53
- else
54
- raise "Command failed (exit #{status.exitstatus}): #{cmd}"
55
- end
56
- end
57
- end
58
- end
59
- end
@@ -1,71 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'json'
4
- require 'net/http'
5
- require 'uri'
6
-
7
- module Buildkite
8
- module Builder
9
- class Github
10
- BASE_URI = URI('https://api.github.com').freeze
11
- ACCEPT_HEADER = 'application/vnd.github.v3+json'
12
- LINK_HEADER = 'link'
13
- NEXT_LINK_REGEX = /<(?<uri>.+)>; rel="next"/.freeze
14
- REPO_REGEX = /github\.com(?::|\/)(.*?)(?:\.git)?\z/.freeze
15
- PER_PAGE = 100
16
-
17
- def self.pull_request_files
18
- new.pull_request_files
19
- end
20
-
21
- def initialize(env = ENV)
22
- @env = env
23
- end
24
-
25
- def pull_request_files
26
- files = []
27
- next_uri = URI.join(BASE_URI, "repos/#{repo}/pulls/#{pull_request_number}/files?per_page=#{PER_PAGE}")
28
-
29
- while next_uri
30
- response = request(next_uri)
31
- files.concat(JSON.parse(response.body))
32
- next_uri = parse_next_uri(response)
33
- end
34
-
35
- files
36
- end
37
-
38
- private
39
-
40
- def repo
41
- Buildkite.env.repo[REPO_REGEX, 1]
42
- end
43
-
44
- def token
45
- @env.fetch('GITHUB_API_TOKEN')
46
- end
47
-
48
- def pull_request_number
49
- Buildkite.env.pull_request
50
- end
51
-
52
- def request(uri)
53
- Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
54
- request = Net::HTTP::Get.new(uri)
55
- request['Authorization'] = "token #{token}"
56
- request['Accept'] = ACCEPT_HEADER
57
-
58
- http.request(request)
59
- end
60
- end
61
-
62
- def parse_next_uri(response)
63
- links = response[LINK_HEADER]
64
- return unless links
65
-
66
- matches = links.match(NEXT_LINK_REGEX)
67
- URI.parse(matches[:uri]) if matches
68
- end
69
- end
70
- end
71
- end
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Buildkite
4
- module Builder
5
- module Loaders
6
- class Manifests < Abstract
7
- MANIFESTS_PATH = Pathname.new('manifests').freeze
8
-
9
- def load
10
- return unless manifests_path.directory?
11
-
12
- manifests_path.children.map do |file|
13
- add(file.basename, Manifest.new(Buildkite::Builder.root, file.readlines))
14
- end
15
- end
16
-
17
- def manifests_path
18
- root.join(MANIFESTS_PATH)
19
- end
20
- end
21
- end
22
- end
23
- end
@@ -1,51 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'set'
4
- require 'pathname'
5
-
6
- module Buildkite
7
- module Builder
8
- class Manifest::Rule
9
- GLOB_OPTIONS = File::FNM_PATHNAME | File::FNM_DOTMATCH | File::FNM_EXTGLOB
10
-
11
- attr_reader :exclude
12
- attr_reader :glob
13
-
14
- def initialize(root, pattern)
15
- @root = Pathname.new(root)
16
- @exclude = false
17
- @glob = @root
18
-
19
- if pattern[0] == '!'
20
- @exclude = true
21
- pattern = pattern[1..-1]
22
- end
23
-
24
- if pattern.start_with?('/')
25
- pattern = pattern[1..-1]
26
- else
27
- @glob = @glob.join('**')
28
- end
29
-
30
- @glob = @glob.join(pattern).to_s
31
- end
32
-
33
- def files
34
- @files ||= begin
35
- matched = Dir.glob(glob, GLOB_OPTIONS)
36
- matched.map! { |file| Pathname.new(file) }
37
- matched.reject!(&:directory?)
38
- matched.map! { |file| file.relative_path_from(Builder.root) }
39
- Set.new(matched.sort!)
40
- end
41
- end
42
-
43
- def match?(file)
44
- file = Pathname.new(file)
45
- file = @root.join(file) unless file.absolute?
46
-
47
- File.fnmatch?(glob, file.to_s, GLOB_OPTIONS)
48
- end
49
- end
50
- end
51
- end
@@ -1,88 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'digest/md5'
4
- require 'pathname'
5
-
6
- module Buildkite
7
- module Builder
8
- class Manifest
9
- autoload :Rule, File.expand_path('manifest/rule', __dir__)
10
-
11
- class << self
12
- def resolve(root, patterns)
13
- new(root, Array(patterns)).modified?
14
- end
15
-
16
- def manifests
17
- @manifests ||= {}
18
- end
19
-
20
- def [](name)
21
- manifests[name.to_s]
22
- end
23
-
24
- def []=(name, manifest)
25
- name = name.to_s
26
- if manifests.key?(name)
27
- raise ArgumentError, "manifest #{name} already exists"
28
- end
29
-
30
- manifests[name] = manifest
31
- end
32
- end
33
-
34
- attr_reader :root
35
-
36
- def initialize(root, patterns)
37
- @root = Pathname.new(root)
38
- @root = Buildkite::Builder.root.join(@root) unless @root.absolute?
39
- @patterns = patterns.map(&:to_s)
40
- end
41
-
42
- def modified?
43
- # DO NOT intersect FileResolver with manifest files. If the manifest is
44
- # large, the operation can be expensive. It's always cheaper to loop
45
- # through the changed files and compare them against the rules.
46
- unless defined?(@modified)
47
- @modified = FileResolver.resolve.modified_files.any? do |file|
48
- file = Buildkite::Builder.root.join(file)
49
- inclusion_rules.any? { |rule| rule.match?(file) } &&
50
- exclusion_rules.none? { |rule| rule.match?(file) }
51
- end
52
- end
53
-
54
- @modified
55
- end
56
-
57
- def files
58
- @files ||= (inclusion_rules.map(&:files).reduce(Set.new, :merge) - exclusion_rules.map(&:files).reduce(Set.new, :merge)).sort.to_set
59
- end
60
-
61
- def digest
62
- @digest ||= begin
63
- digests = files.map { |file| Digest::MD5.file(Buildkite::Builder.root.join(file)).hexdigest }
64
- Digest::MD5.hexdigest(digests.join)
65
- end
66
- end
67
-
68
- private
69
-
70
- def rules
71
- @rules ||= @patterns.each_with_object([]) do |pattern, rules|
72
- pattern = pattern.strip
73
- unless pattern.match?(/\A(#|\z)/)
74
- rules << Rule.new(root, pattern)
75
- end
76
- end
77
- end
78
-
79
- def inclusion_rules
80
- @inclusion_rules ||= rules.reject(&:exclude)
81
- end
82
-
83
- def exclusion_rules
84
- @exclusion_rules ||= rules.select(&:exclude)
85
- end
86
- end
87
- end
88
- end