diff --git a/lib/rscons/builder.rb b/lib/rscons/builder.rb index 007c559..867c545 100644 --- a/lib/rscons/builder.rb +++ b/lib/rscons/builder.rb @@ -24,6 +24,14 @@ module Rscons {} end + # Return a set of build features that this builder provides. + # + # @return [Array] + # Set of build features that this builder provides. + def features + [] + end + # Create a BuildTarget object for this build target. # # Builder sub-classes can override this method to manipulate parameters @@ -47,35 +55,17 @@ module Rscons # Return whether this builder object is capable of producing a given target # file name from a given source file name. # - # @overload produces?(target, source, env) - # - # @param target [String] - # The target file name. - # @param source [String] - # The source file name. - # @param env [Environment] - # The Environment. - # - # @overload produces?(options) - # - # @since 1.10.0 - # - # @param options [Hash] - # Options. - # @option options [String] :target - # Target file name. - # @option options [String] :source - # Source file name. - # @option options [Environment] :env - # The Environment. - # @option options [Hash] :features - # Features that this builder must satisfy. - # See {Environment#register_builds}. + # @param target [String] + # The target file name. + # @param source [String] + # The source file name. + # @param env [Environment] + # The Environment. # # @return [Boolean] # Whether this builder object is capable of producing a given target # file name from a given source file name. - def produces?(options) + def produces?(target, source, env) false end diff --git a/lib/rscons/builders/object.rb b/lib/rscons/builders/object.rb index b91916b..fd5c1ad 100644 --- a/lib/rscons/builders/object.rb +++ b/lib/rscons/builders/object.rb @@ -62,16 +62,18 @@ module Rscons # Return whether this builder object is capable of producing a given target # file name from a given source file name. # - # @param options [Hash] - # Options. + # @param target [String] + # The target file name. + # @param source [String] + # The source file name. + # @param env [Environment] + # The Environment. # # @return [Boolean] # Whether this builder object is capable of producing a given target # file name from a given source file name. - def produces?(options) - target, source, env, features = options.values_at(:target, :source, :env, :features) - (not features[:shared]) and - target.end_with?(*env['OBJSUFFIX']) and + def produces?(target, source, env) + target.end_with?(*env['OBJSUFFIX']) and KNOWN_SUFFIXES.find do |compiler, suffix_var| source.end_with?(*env[suffix_var]) end diff --git a/lib/rscons/builders/shared_library.rb b/lib/rscons/builders/shared_library.rb index 86b9715..7c05620 100644 --- a/lib/rscons/builders/shared_library.rb +++ b/lib/rscons/builders/shared_library.rb @@ -21,6 +21,14 @@ module Rscons } end + # Return a set of build features that this builder provides. + # + # @return [Array] + # Set of build features that this builder provides. + def features + %w[shared] + end + # Create a BuildTarget object for this build target. # # The build target filename is given a platform-dependent suffix if no @@ -61,7 +69,7 @@ module Rscons suffixes = env.expand_varref(["${OBJSUFFIX}", "${LIBSUFFIX}"], vars) # Register builders to build each source to an object file or library. env.register_builds(target, sources, suffixes, vars, - features: {shared: true}) + features: %w[shared]) end # Run the builder to produce a build target. diff --git a/lib/rscons/builders/shared_object.rb b/lib/rscons/builders/shared_object.rb index 1f58d28..a119567 100644 --- a/lib/rscons/builders/shared_object.rb +++ b/lib/rscons/builders/shared_object.rb @@ -37,19 +37,29 @@ module Rscons } end + # Return a set of build features that this builder provides. + # + # @return [Array] + # Set of build features that this builder provides. + def features + %w[shared] + end + # Return whether this builder object is capable of producing a given target # file name from a given source file name. # - # @param options [Hash] - # Options. + # @param target [String] + # The target file name. + # @param source [String] + # The source file name. + # @param env [Environment] + # The Environment. # # @return [Boolean] # Whether this builder object is capable of producing a given target # file name from a given source file name. - def produces?(options) - target, source, env, features = options.values_at(:target, :source, :env, :features) - features[:shared] and - target.end_with?(*env['OBJSUFFIX']) and + def produces?(target, source, env) + target.end_with?(*env['OBJSUFFIX']) and KNOWN_SUFFIXES.find do |compiler, suffix_var| source.end_with?(*env[suffix_var]) end diff --git a/lib/rscons/environment.rb b/lib/rscons/environment.rb index 5a6f426..28df144 100644 --- a/lib/rscons/environment.rb +++ b/lib/rscons/environment.rb @@ -236,15 +236,15 @@ module Rscons # Suffix, including "." if desired. # @param options [Hash] # Extra options. - # @option options [Hash] :features + # @option options [Array] :features # Builder features to be used for this build. See {#register_builds}. # # @return [String] # The file name to be built from +source_fname+ with suffix +suffix+. def get_build_fname(source_fname, suffix, options = {}) build_fname = Rscons.set_suffix(source_fname, suffix).gsub('\\', '/') - options[:features] ||= {} - extra_path = options[:features][:shared] ? "/_shared" : "" + options[:features] ||= [] + extra_path = options[:features].include?("shared") ? "/_shared" : "" found_match = @build_dirs.find do |src_dir, obj_dir| if src_dir.is_a?(Regexp) build_fname.sub!(src_dir, "#{obj_dir}#{extra_path}") @@ -516,10 +516,7 @@ module Rscons converted = nil suffixes.each do |suffix| converted_fname = get_build_fname(source, suffix) - builder = @builders.values.find do |builder| - builder_produces?(builder, target: converted_fname, source: source) - end - if builder + if builder = find_builder_for(converted_fname, source, []) converted = run_builder(builder, converted_fname, [source], cache, vars) return nil unless converted break @@ -548,13 +545,16 @@ module Rscons # Extra variables to pass to the builders. # @param options [Hash] # Extra options. - # @option options [Hash] :features - # Set of features the builder must provide. - # * :shared - builder builds a shared object/library + # @option options [Array] :features + # Set of features the builder must provide. Each feature can be proceeded + # by a "-" character to indicate that the builder must /not/ provide the + # given feature. + # * shared - builder builds a shared object/library # # @return [Array] # List of the output file name(s). def register_builds(target, sources, suffixes, vars, options = {}) + options[:features] ||= [] @registered_build_dependencies[target] ||= Set.new sources.map do |source| if source.end_with?(*suffixes) @@ -563,14 +563,7 @@ module Rscons output_fname = nil suffixes.each do |suffix| attempt_output_fname = get_build_fname(source, suffix, features: options[:features]) - builder = @builders.values.find do |builder| - builder_produces?( - builder, - target: attempt_output_fname, - source: source, - features: options[:features]) - end - if builder + if builder = find_builder_for(attempt_output_fname, source, options[:features]) output_fname = attempt_output_fname self.__send__(builder.name, output_fname, source, vars) @registered_build_dependencies[target] << output_fname @@ -955,31 +948,43 @@ module Rscons tc: tc)) end - # Determine if a builder produces an output file of a given name with - # requested features. + # Find a builder that meets the requested features and produces a target + # of the requested name. + # + # @param target [String] + # Target file name. + # @param source [String] + # Source file name. + # @param features [Array] + # See {#register_builds}. + # + # @return [Builder, nil] + # The builder found, if any. + def find_builder_for(target, source, features) + @builders.values.find do |builder| + features_met?(builder, features) and builder.produces?(target, source, self) + end + end + + # Determine if a builder meets the requested features. # # @param builder [Builder] # The builder. - # @param options [Hash] - # Options for {Builder#produces?}. - # @option options [String] :target - # Target file name. - # @option options [String] :source - # Source file name. - # @option options [Hash] :features - # Builder features. See {#register_builds}. + # @param features [Array] + # See {#register_builds}. # # @return [Boolean] - # Whether the builder produces a file of the given name from the source - # given with the features given. - def builder_produces?(builder, options) - if builder.method(:produces?).arity == 3 - builder.produces?(options[:target], options[:source], self) - else - options = options.dup - options[:features] ||= {} - options[:env] = self - builder.produces?(options) + # Whether the builder meets the requested features. + def features_met?(builder, features) + builder_features = builder.features + features.all? do |feature| + want_feature = true + if feature =~ /^-(.*)$/ + want_feature = false + feature = $1 + end + builder_has_feature = builder_features.include?(feature) + want_feature ? builder_has_feature : !builder_has_feature end end diff --git a/spec/rscons/environment_spec.rb b/spec/rscons/environment_spec.rb index c087ff8..8e68628 100644 --- a/spec/rscons/environment_spec.rb +++ b/spec/rscons/environment_spec.rb @@ -334,6 +334,28 @@ module Rscons end end + describe "#features_met?" do + it "returns true when the builder provides all requested features" do + builder = Struct.new(:features).new(%w[shared other]) + expect(subject.__send__(:features_met?, builder, %w[shared])).to be_truthy + end + + it "returns true when no features are requested" do + builder = Struct.new(:features).new([]) + expect(subject.__send__(:features_met?, builder, [])).to be_truthy + end + + it "returns false when a builder does not provide a requested feature" do + builder = Struct.new(:features).new(%w[shared other]) + expect(subject.__send__(:features_met?, builder, %w[other2])).to be_falsey + end + + it "returns false when a builder provides a feature that is not desired" do + builder = Struct.new(:features).new(%w[shared other]) + expect(subject.__send__(:features_met?, builder, %w[-shared])).to be_falsey + end + end + describe ".parse_makefile_deps" do it 'handles dependencies on one line' do expect(File).to receive(:read).with('makefile').and_return(<