diff --git a/lib/rscons.rb b/lib/rscons.rb index dddf970..6027d48 100644 --- a/lib/rscons.rb +++ b/lib/rscons.rb @@ -3,10 +3,10 @@ require_relative "rscons/application" require_relative "rscons/basic_environment" require_relative "rscons/builder" require_relative "rscons/builder_builder" +require_relative "rscons/builder_set" require_relative "rscons/cache" require_relative "rscons/configure_op" require_relative "rscons/environment" -require_relative "rscons/job_set" require_relative "rscons/script" require_relative "rscons/threaded_command" require_relative "rscons/util" diff --git a/lib/rscons/builder_set.rb b/lib/rscons/builder_set.rb new file mode 100644 index 0000000..2d79255 --- /dev/null +++ b/lib/rscons/builder_set.rb @@ -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] + # 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 diff --git a/lib/rscons/environment.rb b/lib/rscons/environment.rb index ac24b64..4209ea9 100644 --- a/lib/rscons/environment.rb +++ b/lib/rscons/environment.rb @@ -70,7 +70,7 @@ module Rscons @threaded_commands = Set.new @registered_build_dependencies = {} @side_effects = {} - @job_set = JobSet.new(@registered_build_dependencies, @side_effects) + @builder_set = BuilderSet.new(@registered_build_dependencies, @side_effects) @user_deps = {} # Hash of builder name (String) => builder class (Class). @builders = {} @@ -260,16 +260,16 @@ module Rscons end failure = nil begin - while @job_set.size > 0 or @threaded_commands.size > 0 + while @builder_set.size > 0 or @threaded_commands.size > 0 if failure - @job_set.clear! + @builder_set.clear builder = nil else targets_still_building = @threaded_commands.map do |tc| tc.builder.target end - builder = @job_set.get_next_job_to_run(targets_still_building) + builder = @builder_set.get_next_builder_to_run(targets_still_building) end # TODO: have Cache determine when checksums may be invalid based on @@ -328,7 +328,7 @@ module Rscons # # @return [void] def clear_targets - @job_set.clear! + @builder_set.clear end # Define a build target. @@ -356,7 +356,7 @@ module Rscons cache: Cache.instance, env: self, vars: vars) - @job_set.add_job(builder) + @builder_set << builder builder else super diff --git a/lib/rscons/job_set.rb b/lib/rscons/job_set.rb deleted file mode 100644 index 6b0254b..0000000 --- a/lib/rscons/job_set.rb +++ /dev/null @@ -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] - # 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