replace JobSet with BuilderSet

This commit is contained in:
Josh Holtrop 2019-02-17 14:17:31 -05:00
parent ee1640008c
commit 8426a54a57
4 changed files with 82 additions and 94 deletions

View File

@ -3,10 +3,10 @@ require_relative "rscons/application"
require_relative "rscons/basic_environment" require_relative "rscons/basic_environment"
require_relative "rscons/builder" require_relative "rscons/builder"
require_relative "rscons/builder_builder" require_relative "rscons/builder_builder"
require_relative "rscons/builder_set"
require_relative "rscons/cache" require_relative "rscons/cache"
require_relative "rscons/configure_op" require_relative "rscons/configure_op"
require_relative "rscons/environment" require_relative "rscons/environment"
require_relative "rscons/job_set"
require_relative "rscons/script" require_relative "rscons/script"
require_relative "rscons/threaded_command" require_relative "rscons/threaded_command"
require_relative "rscons/util" require_relative "rscons/util"

75
lib/rscons/builder_set.rb Normal file
View File

@ -0,0 +1,75 @@
module Rscons
# Class to keep track of a set of builders to be executed.
class BuilderSet < Hash
# Create a BuilderSet.
#
# @param build_dependencies [Hash]
# Hash mapping targets to a set of build dependencies. A builder will not
# be returned as ready to run if any of its dependencies are still
# building.
# @param side_effects [Hash]
# Hash mapping targets to a set of side-effect files. A builder will not
# be returned as ready to run if any of its dependencies is a side-effect
# of another target that has not yet been built.
def initialize(build_dependencies, side_effects)
super()
@build_dependencies = build_dependencies
@side_effects = side_effects
end
# Add a builder to the BuilderSet.
#
# @param builder [Builder]
# The {Builder} that will produce the target.
def <<(builder)
# We allow multiple builders to be registered per target for cases like:
# env.Directory("dest")
# env.Install("dest", "bin")
# env.Install("dest", "share")
self[builder.target] ||= []
self[builder.target] << builder
end
# Get the next builder that is ready to run from the BuilderSet.
#
# This method will remove the builder from the BuilderSet.
#
# @param targets_still_building [Array<String>]
# Targets that are not finished building. This is used to avoid returning
# a builder as available to run if it depends on one of the targets that
# are still building as a source.
#
# @return [nil, Builder]
# The next builder to run.
def get_next_builder_to_run(targets_still_building)
not_built_yet = targets_still_building + self.keys
not_built_yet += not_built_yet.reduce([]) do |result, target|
result + (@side_effects[target] || [])
end
target_to_build = self.keys.find do |target|
deps = self[target][0].sources + (@build_dependencies[target] || []).to_a
!deps.find {|dep| not_built_yet.include?(dep)}
end
if target_to_build
builder = self[target_to_build][0]
if self[target_to_build].size > 1
self[target_to_build].slice!(0)
else
self.delete(target_to_build)
end
return builder
end
# If there is a builder to run, and nothing is still building, but we did
# not find a builder to run above, then there might be a circular
# dependency introduced by the user.
if (self.size > 0) and targets_still_building.empty?
raise "Could not find a runnable builder. Possible circular dependency for #{self.keys.first}"
end
end
end
end

View File

@ -70,7 +70,7 @@ module Rscons
@threaded_commands = Set.new @threaded_commands = Set.new
@registered_build_dependencies = {} @registered_build_dependencies = {}
@side_effects = {} @side_effects = {}
@job_set = JobSet.new(@registered_build_dependencies, @side_effects) @builder_set = BuilderSet.new(@registered_build_dependencies, @side_effects)
@user_deps = {} @user_deps = {}
# Hash of builder name (String) => builder class (Class). # Hash of builder name (String) => builder class (Class).
@builders = {} @builders = {}
@ -260,16 +260,16 @@ module Rscons
end end
failure = nil failure = nil
begin begin
while @job_set.size > 0 or @threaded_commands.size > 0 while @builder_set.size > 0 or @threaded_commands.size > 0
if failure if failure
@job_set.clear! @builder_set.clear
builder = nil builder = nil
else else
targets_still_building = @threaded_commands.map do |tc| targets_still_building = @threaded_commands.map do |tc|
tc.builder.target tc.builder.target
end end
builder = @job_set.get_next_job_to_run(targets_still_building) builder = @builder_set.get_next_builder_to_run(targets_still_building)
end end
# TODO: have Cache determine when checksums may be invalid based on # TODO: have Cache determine when checksums may be invalid based on
@ -328,7 +328,7 @@ module Rscons
# #
# @return [void] # @return [void]
def clear_targets def clear_targets
@job_set.clear! @builder_set.clear
end end
# Define a build target. # Define a build target.
@ -356,7 +356,7 @@ module Rscons
cache: Cache.instance, cache: Cache.instance,
env: self, env: self,
vars: vars) vars: vars)
@job_set.add_job(builder) @builder_set << builder
builder builder
else else
super super

View File

@ -1,87 +0,0 @@
module Rscons
# Class to keep track of a set of jobs that need to be performed.
class JobSet
# Create a JobSet
#
# @param build_dependencies [Hash]
# Hash mapping targets to a set of build dependencies. A job will not be
# returned as ready to run if any of its dependencies are still building.
# @param side_effects [Hash]
# Hash mapping targets to a set of side-effect files. A job will not be
# returned as ready to run if any of its dependencies is a side-effect
# of another target that has not yet been built.
def initialize(build_dependencies, side_effects)
@jobs = {}
@build_dependencies = build_dependencies
@side_effects = side_effects
end
# Add a job to the JobSet.
#
# @param builder [Builder]
# The {Builder} that will produce the target.
def add_job(builder)
# We allow multiple jobs to be registered per target for cases like:
# env.Directory("dest")
# env.Install("dest", "bin")
# env.Install("dest", "share")
@jobs[builder.target] ||= []
@jobs[builder.target] << builder
end
# Get the next job that is ready to run from the JobSet.
#
# This method will remove the job from the JobSet.
#
# @param targets_still_building [Array<String>]
# Targets that are not finished building. This is used to avoid returning
# a job as available to run if it depends on one of the targets that are
# still building as a source.
#
# @return [nil, Builder]
# The next job to run.
def get_next_job_to_run(targets_still_building)
not_built_yet = targets_still_building + @jobs.keys
not_built_yet += not_built_yet.reduce([]) do |result, target|
result + (@side_effects[target] || [])
end
target_to_build = @jobs.keys.find do |target|
deps = @jobs[target][0].sources + (@build_dependencies[target] || []).to_a
!deps.find {|dep| not_built_yet.include?(dep)}
end
if target_to_build
builder = @jobs[target_to_build][0]
if @jobs[target_to_build].size > 1
@jobs[target_to_build].slice!(0)
else
@jobs.delete(target_to_build)
end
return builder
end
# If there is a job to run, and nothing is still building, but we did
# not find a job to run above, then there might be a circular dependency
# introduced by the user.
if (@jobs.size > 0) and targets_still_building.empty?
raise "Could not find a runnable job. Possible circular dependency for #{@jobs.keys.first}"
end
end
# Remove all jobs from the JobSet.
def clear!
@jobs.clear
end
# Get the JobSet size.
#
# @return [Integer]
# JobSet size.
def size
@jobs.size
end
end
end