def process
      cache = Cache.instance
      failure = nil
      begin
        while @job_set.size > 0 or @threaded_commands.size > 0

          if failure
            @job_set.clear!
            job = nil
          else
            targets_still_building = @threaded_commands.map do |tc|
              tc.build_operation[:target]
            end
            job = @job_set.get_next_job_to_run(targets_still_building)
          end

          # TODO: have Cache determine when checksums may be invalid based on
          # file size and/or timestamp.
          cache.clear_checksum_cache!

          if job
            result = run_builder(job[:builder],
                                 job[:target],
                                 job[:sources],
                                 cache,
                                 job[:vars],
                                 allow_delayed_execution: true,
                                 setup_info: job[:setup_info])
            unless result
              failure = "Failed to build #{job[:target]}"
              Ansi.write($stderr, :red, failure, :reset, "\n")
              next
            end
          end

          completed_tcs = Set.new
          # First do a non-blocking wait to pick up any threads that have
          # completed since last time.
          while tc = wait_for_threaded_commands(nonblock: true)
            completed_tcs << tc
          end

          # If needed, do a blocking wait.
          if (@threaded_commands.size > 0) and
             ((completed_tcs.empty? and job.nil?) or (@threaded_commands.size >= n_threads))
            completed_tcs << wait_for_threaded_commands
          end

          # Process all completed {ThreadedCommand} objects.
          completed_tcs.each do |tc|
            result = finalize_builder(tc)
            if result
              @build_hooks[:post].each do |build_hook_block|
                build_hook_block.call(tc.build_operation)
              end
            else
              unless @echo == :command
                print_failed_command(tc.command)
              end
              failure = "Failed to build #{tc.build_operation[:target]}"
              Ansi.write($stderr, :red, failure, :reset, "\n")
              break
            end
          end

        end
      ensure
        cache.write
      end
      if failure
        raise BuildError.new(failure)
      end
    end
def process
      cache = Cache.instance
      failure = nil
      begin
        while @job_set.size > 0 or @threaded_commands.size > 0

          if failure
            @job_set.clear!
            job = nil
          else
            targets_still_building = @threaded_commands.map do |tc|
              tc.build_operation[:target]
            end
            job = @job_set.get_next_job_to_run(targets_still_building)
          end

          # TODO: have Cache determine when checksums may be invalid based on
          # file size and/or timestamp.
          cache.clear_checksum_cache!

          if job
            result = run_builder(job[:builder],
                                 job[:target],
                                 job[:sources],
                                 cache,
                                 job[:vars],
                                 allow_delayed_execution: true,
                                 setup_info: job[:setup_info])
            unless result
              failure = "Failed to build #{job[:target]}"
              Ansi.write($stderr, :red, failure, :reset, "\n")
              next
            end
          end

          completed_tcs = Set.new
          # First do a non-blocking wait to pick up any threads that have
          # completed since last time.
          while tc = wait_for_threaded_commands(nonblock: true)
            completed_tcs << tc
          end

          # If needed, do a blocking wait.
          if (@threaded_commands.size > 0) and
             ((completed_tcs.empty? and job.nil?) or (@threaded_commands.size >= n_threads))
            completed_tcs << wait_for_threaded_commands
          end

          # Process all completed {ThreadedCommand} objects.
          completed_tcs.each do |tc|
            result = finalize_builder(tc)
            if result
              @build_hooks[:post].each do |build_hook_block|
                build_hook_block.call(tc.build_operation)
              end
            else
              unless @echo == :command
                print_failed_command(tc.command)
              end
              failure = "Failed to build #{tc.build_operation[:target]}"
              Ansi.write($stderr, :red, failure, :reset, "\n")
              break
            end
          end

        end
      ensure
        cache.write
      end
      if failure
        raise BuildError.new(failure)
      end
    end
Build all build targets specified in the Environment.

 When a block is passed to Environment.new, this method is automatically
 called after the block returns.

 @return [void]