From df52a7e0e1846f4dc15aa28987b2ec8171d83cc2 Mon Sep 17 00:00:00 2001 From: Josh Holtrop Date: Tue, 6 Jun 2017 13:27:38 -0400 Subject: [PATCH] add SharedObject and SharedLibrary builders - close #8 --- build_tests/shared_library/Rsconsfile | 11 +++ build_tests/shared_library/src/lib/one.c | 6 ++ build_tests/shared_library/src/lib/one.h | 5 ++ build_tests/shared_library/src/lib/two.c | 6 ++ build_tests/shared_library/src/lib/two.h | 5 ++ build_tests/shared_library/src/main.c | 8 ++ lib/rscons.rb | 4 + lib/rscons/builders/shared_library.rb | 103 +++++++++++++++++++++++ lib/rscons/builders/shared_object.rb | 103 +++++++++++++++++++++++ spec/build_tests_spec.rb | 16 ++++ 10 files changed, 267 insertions(+) create mode 100644 build_tests/shared_library/Rsconsfile create mode 100644 build_tests/shared_library/src/lib/one.c create mode 100644 build_tests/shared_library/src/lib/one.h create mode 100644 build_tests/shared_library/src/lib/two.c create mode 100644 build_tests/shared_library/src/lib/two.h create mode 100644 build_tests/shared_library/src/main.c create mode 100644 lib/rscons/builders/shared_library.rb create mode 100644 lib/rscons/builders/shared_object.rb diff --git a/build_tests/shared_library/Rsconsfile b/build_tests/shared_library/Rsconsfile new file mode 100644 index 0000000..8ff5182 --- /dev/null +++ b/build_tests/shared_library/Rsconsfile @@ -0,0 +1,11 @@ +Rscons::Environment.new do |env| + env["CPPPATH"] << "src/lib" + libmine = env.SharedLibrary("libmine", Dir["src/lib/*.c"]) + env.Program("test-shared.exe", + Dir["src/*.c"], + "LIBPATH" => %w[.], + "LIBS" => %w[mine]) + env.build_after("test-shared.exe", libmine.to_s) + env.Program("test-static.exe", + Dir["src/**/*.c"]) +end diff --git a/build_tests/shared_library/src/lib/one.c b/build_tests/shared_library/src/lib/one.c new file mode 100644 index 0000000..2e57a33 --- /dev/null +++ b/build_tests/shared_library/src/lib/one.c @@ -0,0 +1,6 @@ +#include + +void one(void) +{ + printf("Hi from one()\n"); +} diff --git a/build_tests/shared_library/src/lib/one.h b/build_tests/shared_library/src/lib/one.h new file mode 100644 index 0000000..3cce3a4 --- /dev/null +++ b/build_tests/shared_library/src/lib/one.h @@ -0,0 +1,5 @@ +#ifndef ONE_H + +void one(void); + +#endif diff --git a/build_tests/shared_library/src/lib/two.c b/build_tests/shared_library/src/lib/two.c new file mode 100644 index 0000000..17873f8 --- /dev/null +++ b/build_tests/shared_library/src/lib/two.c @@ -0,0 +1,6 @@ +#include + +void two(void) +{ + printf("Hi from two()\n"); +} diff --git a/build_tests/shared_library/src/lib/two.h b/build_tests/shared_library/src/lib/two.h new file mode 100644 index 0000000..657827c --- /dev/null +++ b/build_tests/shared_library/src/lib/two.h @@ -0,0 +1,5 @@ +#ifndef TWO_H + +void two(void); + +#endif diff --git a/build_tests/shared_library/src/main.c b/build_tests/shared_library/src/main.c new file mode 100644 index 0000000..600363c --- /dev/null +++ b/build_tests/shared_library/src/main.c @@ -0,0 +1,8 @@ +#include "one.h" +#include "two.h" + +int main(int argc, char * argv[]) +{ + one(); + two(); +} diff --git a/lib/rscons.rb b/lib/rscons.rb index 7af226f..609f9cc 100644 --- a/lib/rscons.rb +++ b/lib/rscons.rb @@ -17,6 +17,8 @@ require_relative "rscons/builders/library" require_relative "rscons/builders/object" require_relative "rscons/builders/preprocess" require_relative "rscons/builders/program" +require_relative "rscons/builders/shared_library" +require_relative "rscons/builders/shared_object" require_relative "rscons/builders/simple_builder" # Namespace module for rscons classes @@ -35,6 +37,8 @@ module Rscons :Object, :Preprocess, :Program, + :SharedLibrary, + :SharedObject, ] # Class to represent a fatal error while building a target. diff --git a/lib/rscons/builders/shared_library.rb b/lib/rscons/builders/shared_library.rb new file mode 100644 index 0000000..0af06b1 --- /dev/null +++ b/lib/rscons/builders/shared_library.rb @@ -0,0 +1,103 @@ +module Rscons + module Builders + # A default Rscons builder that knows how to link object files into a + # shared library. + class SharedLibrary < Builder + + # Return default construction variables for the builder. + # + # @param env [Environment] The Environment using the builder. + # + # @return [Hash] Default construction variables for the builder. + def default_variables(env) + { + 'SHLIBSUFFIX' => (RUBY_PLATFORM =~ /mingw/ ? '.dll' : '.so'), + 'SHLDFLAGS' => ['${LDFLAGS}', '-shared'], + 'SHLD' => nil, + 'SHLIBDIRPREFIX' => '-L', + 'SHLIBLINKPREFIX' => '-l', + 'SHLDCMD' => ['${SHLD}', '-o', '${_TARGET}', '${SHLDFLAGS}', '${_SOURCES}', '${SHLIBDIRPREFIX}${LIBPATH}', '${SHLIBLINKPREFIX}${LIBS}'] + } + end + + # Create a BuildTarget object for this build target. + # + # The build target filename is given a platform-dependent suffix if no + # other suffix is given. + # + # @param options [Hash] + # Options to create the BuildTarget with. + # @option options [Environment] :env + # The Environment. + # @option options [String] :target + # The user-supplied target name. + # @option options [Array] :sources + # The user-supplied source file name(s). + # + # @return [BuildTarget] + def create_build_target(options) + env, target, vars = options.values_at(:env, :target, :vars) + my_options = options.dup + unless env.expand_varref(target, vars) =~ /\./ + my_options[:target] += env.expand_varref("${SHLIBSUFFIX}", vars) + end + super(my_options) + end + + # Set up a build operation using this builder. + # + # @param options [Hash] Builder setup options. + # + # @return [Object] + # Any object that the builder author wishes to be saved and passed back + # in to the {#run} method. + def setup(options) + target, sources, env, vars = options.values_at(:target, :sources, :env, :vars) + 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}) + end + + # Run the builder to produce a build target. + # + # @param options [Hash] Builder run options. + # + # @return [String,false] + # Name of the target file on success or false on failure. + def run(options) + target, sources, cache, env, vars, objects = options.values_at(:target, :sources, :cache, :env, :vars, :setup_info) + ld = env.expand_varref("${SHLD}", vars) + ld = if ld != "" + ld + elsif sources.find {|s| s.end_with?(*env.expand_varref("${DSUFFIX}", vars))} + "${SHDC}" + elsif sources.find {|s| s.end_with?(*env.expand_varref("${CXXSUFFIX}", vars))} + "${SHCXX}" + else + "${SHCC}" + end + vars = vars.merge({ + '_TARGET' => target, + '_SOURCES' => objects, + 'SHLD' => ld, + }) + options[:sources] = objects + command = env.build_command("${SHLDCMD}", vars) + standard_threaded_build("SHLD #{target}", target, command, objects, env, cache) + end + + # Finalize a build. + # + # @param options [Hash] + # Finalize options. + # + # @return [String, nil] + # The target name on success or nil on failure. + def finalize(options) + standard_finalize(options) + end + + end + end +end diff --git a/lib/rscons/builders/shared_object.rb b/lib/rscons/builders/shared_object.rb new file mode 100644 index 0000000..043ff4c --- /dev/null +++ b/lib/rscons/builders/shared_object.rb @@ -0,0 +1,103 @@ +module Rscons + module Builders + # A default Rscons builder which knows how to produce an object file which + # is capable of being linked into a shared library from various types of + # source files. + class SharedObject < Builder + + # Mapping of known sources from which to build object files. + KNOWN_SUFFIXES = { + "AS" => "ASSUFFIX", + "SHCC" => "CSUFFIX", + "SHCXX" => "CXXSUFFIX", + "SHDC" => "DSUFFIX", + } + + # Return default construction variables for the builder. + # + # @param env [Environment] The Environment using the builder. + # + # @return [Hash] Default construction variables for the builder. + def default_variables(env) + pic_flags = (RUBY_PLATFORM =~ /mingw/ ? [] : ['-fPIC']) + { + 'SHCCFLAGS' => ['${CCFLAGS}'] + pic_flags, + + 'SHCC' => '${CC}', + 'SHCFLAGS' => [], + 'SHCCCMD' => ['${SHCC}', '-c', '-o', '${_TARGET}', '${CCDEPGEN}', '${INCPREFIX}${CPPPATH}', '${CPPFLAGS}', '${SHCFLAGS}', '${SHCCFLAGS}', '${_SOURCES}'], + + 'SHCXX' => '${CXX}', + 'SHCXXFLAGS' => [], + 'SHCXXCMD' =>['${SHCXX}', '-c', '-o', '${_TARGET}', '${CXXDEPGEN}', '${INCPREFIX}${CPPPATH}', '${CPPFLAGS}', '${SHCXXFLAGS}', '${SHCCFLAGS}', '${_SOURCES}'], + + 'SHDC' => 'gdc', + 'SHDFLAGS' => ['${DFLAGS}'] + pic_flags, + 'SHDCCMD' => ['${SHDC}', '-c', '-o', '${_TARGET}', '${INCPREFIX}${D_IMPORT_PATH}', '${SHDFLAGS}', '${_SOURCES}'], + } + 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. + # + # @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 + KNOWN_SUFFIXES.find do |compiler, suffix_var| + source.end_with?(*env[suffix_var]) + end + end + + # Run the builder to produce a build target. + # + # @param options [Hash] Builder run options. + # + # @return [String, ThreadedCommand] + # Target file name if target is up to date or a {ThreadedCommand} + # to execute to build the target. + def run(options) + target, sources, cache, env, vars = options.values_at(:target, :sources, :cache, :env, :vars) + vars = vars.merge({ + '_TARGET' => target, + '_SOURCES' => sources, + '_DEPFILE' => Rscons.set_suffix(target, env.expand_varref("${DEPFILESUFFIX}", vars)), + }) + com_prefix = KNOWN_SUFFIXES.find do |compiler, suffix_var| + sources.first.end_with?(*env.expand_varref("${#{suffix_var}}")) + end.tap do |v| + v.nil? and raise "Error: unknown input file type: #{sources.first.inspect}" + end.first + command = env.build_command("${#{com_prefix}CMD}", vars) + # Store vars back into options so new keys are accessible in #finalize. + options[:vars] = vars + standard_threaded_build("#{com_prefix} #{target}", target, command, sources, env, cache) + end + + # Finalize the build operation. + # + # @param options [Hash] Builder finalize options. + # + # @return [String, nil] + # Name of the target file on success or nil on failure. + def finalize(options) + if options[:command_status] + target, deps, cache, env, vars = options.values_at(:target, :sources, :cache, :env, :vars) + if File.exists?(vars['_DEPFILE']) + deps += Environment.parse_makefile_deps(vars['_DEPFILE'], target) + FileUtils.rm_f(vars['_DEPFILE']) + end + cache.register_build(target, options[:tc].command, deps.uniq, env) + target + end + end + + end + end +end diff --git a/spec/build_tests_spec.rb b/spec/build_tests_spec.rb index 271de11..5cd28b3 100644 --- a/spec/build_tests_spec.rb +++ b/spec/build_tests_spec.rb @@ -671,6 +671,22 @@ EOF expect(slines[1]).to eq "LD abs.exe" end + it "creates shared libraries" do + test_dir("shared_library") + + result = run_test + expect(result.stderr).to eq "" + slines = lines(result.stdout) + expect(slines).to include("SHLD libmine.so") + + result = run_test + expect(result.stderr).to eq "" + expect(result.stdout).to eq "" + + expect(`LD_LIBRARY_PATH=. ./test-shared.exe`).to match /Hi from one/ + expect(`./test-static.exe`).to match /Hi from one/ + end + context "backward compatibility" do it "allows a builder to call Environment#run_builder in a non-threaded manner" do test_dir("simple")