require 'rbconfig' require 'fileutils' # some global variables: $LOCAL_LIBS = "" # A string extension from old mkmf.rb class String def quote /\s/ =~ self ? "\"#{self}\"" : self end # and a new function, that can come in useful in several places: def sanitize return self.tr("a-z./\055", "A-Z___") end end # An array extension from old mkmf.rb class Array def quote map {|s| s.quote} end end =begin rdoc :title: Mkmf2: a better replacement for mkmf =end =begin rdoc =Mkmf2: a drop-in replacement for mkmf Mkmf2 aims at replacing the functionnality of the old mkmf, with a better and cleaner code. It hopefully will keep all the previous mkmf files working, while providing * better control over the Makefiles produced; * installation to $HOME; * uninstallation; * more flexibility. ==Background Mkmf2 was written to supplement a missing feature on the old +mkmf.rb+: it is not possible to install include files along with the library, and not easy to make several libraries, that link each other, and install them... So here comes mkmf2.rb ! ==Bugs There are probably a huge number of bugs, or missing features, or misleading features. In case you find one, please file a request in the mkmf2 project on rubyforge, at the address http://rubyforge.org/tracker/?group_id=1391 But before, please, check out the latest cvs version at http://rubyforge.org/scm/?group_id=1391 and make sure the bug is still there... ==Note to anyone who wishes to contribute Please, document any code you add, and keep the changelog up to date... ==Copyright This module is copyright 2006 by Vincent Fourmond. You can use and redistribute it under the terms of the General Public License version 2. As a special exception, you can distribute any verbatim copy along with a program, provided that this program depends on mkmf2 to install. =end module Mkmf2 include Config # The CVS tag used for the release. CVS_TAG = '$Name$' # The module version; it is computed from CVS_TAG VERSION = CVS_TAG.match(/\D+(.*?)\s*\$?$/)[1].tr('_-','..') # The entities: a list of Mkmf2::MfEntity representing what # we are currently building. @@entities = [] # User-defined rules: @@user_rules = [] # we first start the module with a whole bunch of small subclasses # that will come in really useful later. # This class provides a crude functionnality for defining a rule # in a Makefile. class MfRule attr_accessor :name, :rule, :dependencies # converts this rule to a string that can be readily printed to # the Makefile def to_s return "#{@name}: #{@dependencies.join ' '}" + if @rule.empty? # we don't need a second line "\n" else "\n\t#{@rule.join "\n\t"}\n" end end # +name+:: the name of the rule. # +rule+:: # a string or an array of lines; strings are split if necessary # +deps+:: a string or an array of dependencies def initialize(name, rule = nil, deps = nil) @name = name if rule.is_a? Array @rule = rule elsif rule.nil? @rule = [] else @rule = rule.split /\s*\n\t?\s*/ end if deps.is_a? Array @dependencies = deps elsif deps.nil? @dependencies = [] else @dependencies = deps.split /\s+/ end end end # A small class to include comments into the Makefile, just to help anyone # who has to debug something. And it annoys me not to be able to clearly # read the output of the test.rb script ! class MfComment < MfRule # The actual text of the comment. attr_accessor :text # +str+ is the text meant to be displayed in the makefile. If it is nil # the rule just ends up being a blank line, to allow for easy separation # of the rules. def initialize(str = nil) @text = str end def to_s if @text.nil? return "\n" else lines = @text.to_s.split '\n' lines.unshift "" return "#{lines.join "\n# "}\n" end end end # An MfEntity object reprensents something that is part of the package. # In this way, it needs to provide: # # * a way to *install* itself; # * a way to *uninstall* itself; # * a way to *clean* the source directory; # * and, possibly, a way to *build* itself. # # This class should be subclassed for objects that need building. # It should be enough though for many simple objects. # # The module will hold a list of all the MfEntities, and use them in turn # to create the Makefile. class MfEntity # +install_files+ is a hash that associates files in the building # tree to files where the installation is done. To be more precise # the install path for the files is expressed relative to # +install_path+ attr_accessor :install_files # This name has to be registered by Mkmf2.register_name. Its lowercase # version will be used to name the targets (install_#{name}), etc.), and # it's uppercase version can be used as a prefix to name Makefile # variables, that we can for instance depend on. attr_accessor :name # It represents the type of the entity. It should be a valid entry # for Mkmf2.install_path. attr_accessor :kind # It is used to ask Mkmf2.install_files for the right rule for # installing files (they can be data, scripts, programs, and so on...) attr_accessor :install_rule # The +name+ parameter is turned into a unique name and ends up as # an identifier for the stuffs related to this element in the Makefile. # The +install+ parameter directly becomes install_files. # The +kind+ parameter describes the kind of thing we're going # to install. See Mkmf2.install_path. def initialize(name, install, kind = 'lib', install_rule = 'install_data') # we append kind to avoid overlaps in case of # a malchosen name (even if this is guaranteed by register_name), # it will look better. @name = Mkmf2::register_name(kind + "_" + name) @install_files = install.dup @kind = kind @install_rule = install_rule end # Returns the install path for the current object. The implementation # should call Mkmf2.install_path with an appropriate argument. def install_path return Mkmf2::install_path(@kind) end # Returns an array containing # * a string as the first element which should be appendended # as a dependency for the install target # * a set of MfRules, describing what need to be done # for that peculiar install. def install_rules rules = ["install_#{@name.downcase}"] rules.push MfComment.new("installation rules for #{name}") rules.push MfRule.new("install_#{@name.downcase}", Mkmf2.install_files_rules(@install_rule, install_path, @install_files), ["$(#{@name.upcase}_SOURCE_FILES)", "$(#{@name.upcase}_INSTALL_DIRS)"] ) end # Rules for uninstalling the entity. # See install_rules for the return values. def uninstall_rules rules = ["uninstall_#{@name.downcase}"] rules.push MfComment.new("uninstallation rules for #{name}") files = [] # the rules to remove the installed files dirs = [] # the directories created during install @install_files.each_value do |v| dest = File.join(install_path,v) files.push Mkmf2.rule('remove',dest) dirs << File.dirname(dest) end dirs.uniq! for dir in dirs files << Mkmf2.rule('remove_path',dir) end rules.push MfRule.new("uninstall_#{@name.downcase}", files) end # Rules for cleaning the source directory. See #install_rules for # an explanation about return values. This implementation does # nothing, as there is nothing to be built. def clean_rules return [] end # Rules for building the entity. See #install_rules for # an explanation about return values. def build_rules return [] end # Returns all the variables that should be added to the Makefile. # It should return a simple hash containing the values. def variables # we need to define this variable: ret = {} install = [] source = [] dirs = [] @install_files.each do |k,f| file = File.join(install_path,f) install << file source << k dirs << File.dirname(file) + "/" end dirs.uniq! ret["#{@name.upcase}_INSTALL_FILES"] = Mkmf2.pretty_print_list(install,"#{@name.upcase}_INSTALL_FILES=") ret["#{@name.upcase}_INSTALL_DIRS"] = Mkmf2.pretty_print_list(dirs,"#{@name.upcase}_INSTALL_DIRS=") ret["#{@name.upcase}_SOURCE_FILES"] = Mkmf2.pretty_print_list(source,"#{@name.upcase}_SOURCE_FILES=") return ret end end # The class to create libraries that need to be built (that is basically # why I wrote the whole stuff !). class MfBinaryLibEntity < MfEntity # The source files of the library. attr_accessor :sources # The object files for the library attr_accessor :objects # The name of the file produced in the source directory. attr_accessor :target_file # The libraries on which the linking does depend attr_accessor :lib_depends # Supplementary command-line arguments for the linking step. # It will be used for instance to store library info. attr_accessor :suppl_args # +name+:: of the binary library produce, for instance "Biniou/Bidule" # +sources+:: sources of the files, defaults to all the potential # +C+/+C\+\++ in the current directory. # The sources are further expanded with Dir.glob. # If you give then name of a directory, it will be # expanded # +target_file+:: how the files built in the current directory # will be called. Defaults to the same file name # as +name+. # +lib_depends+:: an array of objects, which designate ruby binary # libraries that this library does depend on; def initialize(name, sources = nil, target_file = nil, lib_depends = nil) if sources.nil? sources = ["."] end @sources = [] add_sources(sources) if target_file.nil? @target_file = File.basename(name) + ".#{CONFIG["DLEXT"]}" else @target_file = target_file end install_hash = { @target_file => name + ".#{CONFIG["DLEXT"]}" } # Then, we call the function of the parent super(name, install_hash,'bin_lib','install_bin') @suppl_args = "" end # Adds source files to the library. Can come in useful inside a block def add_sources(*sources) # we flatten the array, just to make sure everything is top-level sources.flatten! # We expand directory into the files in them # (no subdirs) sources.collect! do |f| if File.directory?(f) temp = [] for ext in Mkmf2.source_files_extensions temp << File.join(f,"*.#{ext}") end temp else f end end # Then, we expand the files: for f in sources files = Dir.glob(f) if files.empty? @sources << f else @sources += files end end # and we update the list of current object files @objects = @sources.collect do |f| f.gsub(/\.[^.]+$/, ".#{CONFIG["OBJEXT"]}") end end # def build_rules return [#"build_#{@name.downcase}", @target_file, # better to write this way... more clear on the # zsh command-line completion ;-) ! MfRule.new(@target_file, Mkmf2.rule('build_library', "$(#{name.upcase}_OBJS)", @suppl_args), "$(#{name.upcase}_OBJS)") ] end # This removes the object files for the target... def clean_rules rules = ["clean_#{@name.downcase}"] rules.push MfComment.new("cleaning rules for #{name}") files = [] # the rules to clean the files @objects.each do |v| files.push Mkmf2.rule('remove',v) end files.push Mkmf2.rule('remove', @target_file) rules.push MfRule.new("clean_#{@name.downcase}", files) return rules end def variables vars = super # We add the object list variable vars["#{name.upcase}_OBJS"] = Mkmf2.pretty_print_list(@objects,"#{name.upcase}_OBJS=") return vars end end # Returns a list of the potential source files extensions. def Mkmf2.source_files_extensions return Mkmf2.cpp_files_extensions + Mkmf2.c_files_extensions end # Return the extensions for C++ files. def Mkmf2.cpp_files_extensions return %w{cpp cc cxx} end # Return extensions for C files. def Mkmf2.c_files_extensions return %w{c} end # This function declares a shared binary library that will be built # and installed using the appropriate functions. # # +libname+ is what is usually passed as an argument to the # +create_makefile+ function of the old # # +sources+ is the source files. They are processed using Dir.glob, so # they can contain wildcards. # # This function contends itselfs to *declare* the library, it does not # *build* it, as this will be done by +make+. def declare_binary_library(libname, *sources, &b) add_entity(MfBinaryLibEntity.new(libname, sources), &b) end def add_entity(entity) yield entity if block_given? @@entities << entity return entity end # This function declares a Ruby library, namely files that want # to be installed directly in the rubylibdir (or equivalent, depending # on the user settings). # +target_dir+:: the target directory, relative to the installation # directory; # +files+:: the files to install. Defaults to "lib/**.rb" # # For instance, # declare_library("Biniou", "toto.rb") # will install the file "Biniou/toto.rb" in the common library # directory. def declare_library(target_dir,*files, &b) if files.empty? files << "lib/**/*.rb" end declare_file_set(target_dir, files, 'lib', false, &b) end # Declares a set of files that should be executed directly be the user. def declare_exec(*files, &b) # No need for a target directory. declare_file_set("", files, 'exec', true, 'install_script',&b) end # Declares a documentation that doesn't need to be built. For the arguments, # see #declare_library. def declare_doc(target_dir, *files,&b) if files.empty? files << "doc/**/*" end declare_file_set(target_dir, files, 'doc', false,&b) end # Declares a set of include files to be installed. Defaults to # all the files in the include subdirectory. def declare_includes(target_dir,*files,&b) if files.empty? files << "include/**/*.h" end declare_file_set(target_dir, files, 'include', true,&b) end # The main place for declaring files to be installed. # +target_dir+:: the target directory relative to the install path # if it is nil or empty, then just install in the # base directory. # +files+:: the source files # +kind+:: the kind of target (see again and again install_path) # +basename+:: wether or not to strip the leading directories ? # +skip+:: a regular expression (or whatever object that has a === # method that matches strings) saying wich files should # be excluded. Defaults to /~$/. # _base_dir_:: a source base directory to be stripped from the target # name def declare_file_set(target_dir, files, kind = 'lib', basename = true, rule = 'install_data', skip = /~$/, delete = /((^.*\/)?lib\/)?/, base_dir = false, &b) source_files = [] for glob in files # If glob looks like a glob, we use Dir.glob, else we add it # without modification if glob =~ /\*|\[|\?/ source_files += Dir[glob] else source_files << glob end end install_hash = {} for f in source_files next if skip === f next if File.directory? f filename = if basename File.basename(f) else f.dup end filename.gsub!(delete, '') if target_dir.nil? or target_dir.empty? install_hash[f] = filename else install_hash[f] = File.join(target_dir, filename) end end add_entity(MfEntity.new(target_dir, install_hash, kind, rule), &b) end @@model = "local" # Sets the model for directory installation. There are for now # three values: # local:: for a standard installation (like the default in mkmf.rb) # dist:: to use in a packageing system # home:: to install to a home directory (basically, prefix= $HOME) # # Please do not modify @@model directly. def Mkmf2.set_model(model = "local") @@model = model end # Sets up a whole bunch of variables necessary for installation, depending # on the current value of the @@model parameter. This is the place where we # setup the variables used by the installation process, namely: # +RUBYLIB_INSTALL_DIR+:: the directory where the text libraries # are to be installed; # +RUBYARCHLIB_INSTALL_DIR+:: the directory where architecture # dependent libraries are installed; # +INCLUDE_INSTALL_DIR+:: the directory where include files should go # if necessary; # And for now, that is all. def setup_model case @@model when "home" # I made a mistake for the home scheme. # we ensure the prefix is set to $HOME MAKEFILE_CONFIG["RUBYLIB_INSTALL_DIR"] = "$(DESTDIR)$(HOME)/lib/ruby" # For the HOME scheme, binaries and .rb files go # to the same directory. MAKEFILE_CONFIG["RUBYARCHLIB_INSTALL_DIR"] = "$(DESTDIR)$(HOME)/lib/ruby" MAKEFILE_CONFIG["INCLUDE_INSTALL_DIR"] = "$(DESTDIR)$(HOME)/include" MAKEFILE_CONFIG["EXEC_INSTALL_DIR"] = "$(DESTDIR)$(HOME)/bin" when "dist" # shares some code with the previous item, don't forget to # update *BOTH* ! MAKEFILE_CONFIG["RUBYLIB_INSTALL_DIR"] = "$(DESTDIR)$(rubylibdir)" MAKEFILE_CONFIG["RUBYARCHLIB_INSTALL_DIR"] = "$(DESTDIR)$(archdir)" MAKEFILE_CONFIG["INCLUDE_INSTALL_DIR"] = "$(DESTDIR)$(includedir)" MAKEFILE_CONFIG["EXEC_INSTALL_DIR"] = File.join("$(prefix)", "bin") when "local" MAKEFILE_CONFIG["RUBYLIB_INSTALL_DIR"] = "$(sitelibdir)" MAKEFILE_CONFIG["RUBYARCHLIB_INSTALL_DIR"] = "$(sitearchdir)" # To make the base directory for installation, I propose # to strip all the directories containing ruby from the # $(sitedir) directory and strip one more directory. basedir = MAKEFILE_CONFIG["sitedir"] while File.basename(basedir) =~ /ruby/ basedir = File.dirname(basedir) end # Strip one more: basedir = File.dirname(basedir) MAKEFILE_CONFIG["basedir"] = basedir MAKEFILE_CONFIG["INCLUDE_INSTALL_DIR"] = "$(basedir)/include" MAKEFILE_CONFIG["EXEC_INSTALL_DIR"] = "$(basedir)/bin" end end # Returns the installation path for a given thing (+what+). This function # basically returns a simple MAKEFILE_CONFIG variable, which has otherwise # been setup by setup_model. What is available for now: # +lib+:: where to install .rb library # +bin_lib+:: where to install binary libs # +include+:: include files def Mkmf2.install_path(what) # we should definitely append a $(DESTDIR) to every single # directory we return, since it will make debugging case what when 'lib' return Mkmf2.config_var('RUBYLIB_INSTALL_DIR') when 'exec' return Mkmf2.config_var('EXEC_INSTALL_DIR') when 'bin_lib' return Mkmf2.config_var('RUBYARCHLIB_INSTALL_DIR') when 'include' return Mkmf2.config_var('INCLUDE_INSTALL_DIR') else raise "Unkown installation directory specification: #{what}" end end @@registered_names = {} # a hash containing already registered names # to avoid namespace clobbering. # Register a new name, making sure the name returned is unique. def Mkmf2.register_name(name) # first, transform all that is not letter into underscore name.gsub!(/[^a-zA-Z]/,'_') # pretty-print a bit name.gsub!(/^_|_$/,'') name.gsub!(/_+/, '_') name.downcase! # then, increment the number if @@registered_names[name] i = 1 while @@registered_names["#{name}_#{i}"] i += 1 end name = "#{name}_#{i}" end @@registered_names[name] = true return name end # Stores which configuration variables are used @@config_variables_used = [] # Returns the given config key, that will be used to write a rule # in the Makefile. For better output, the function does store a list of # all the config variables which are in use, and simply outputs # +$(VARIABLE)+. def Mkmf2.config_var(str) @@config_variables_used << str return "$(#{str})" end # This hash says which of the "CONFIG" variables we should make available # as a global variable -- and use it back to output the variables. If non # nil, the second part says which name it should have as global variable. MKMF_GLOBAL_VARIABLES = { "CFLAGS" => nil, "CXXFLAGS" => nil, "LDFLAGS" => nil, "DLDFLAGS" => nil, "CPPFLAGS" => nil, "LIBS" => nil, "LIBRUBYARG" => nil, "LOCAL_LIBS" => nil, "libs" => nil, } # Takes the config variables, turn them into global variables. def config_to_global for var,name in MKMF_GLOBAL_VARIABLES name = var if name.nil? value = MAKEFILE_CONFIG[var].to_s # make sure its a string # in case it doesn't exist there... # Nice trick to get around quoting... block = eval "proc {|x| $#{name} = x}" block.call(value) end $LIBRUBYARG = "" # seems the default in mkmf.rb end # Takes the global variables taken from the config, and put them back into # the config hash. def global_to_config for var,name in MKMF_GLOBAL_VARIABLES name = var if name.nil? value = eval "$#{name}" MAKEFILE_CONFIG[var] = value end end # A constant to get a configuration variable MAKE_VARIABLE = /\$\((\w+)\)/ # List config variables referenced to by the string _str_. If # hash is specified, only config variables that are keys of this hash # will be listed. def subvars(str, hash = nil) vars = [] str.gsub(MAKE_VARIABLE) { |k| # we add the key to the list only if it exists, else the # environment variable gets overridden by what is written # in the Makefile (for $HOME, for instance) vars << $1 if ( hash.nil? || hash.key?($1) ) } return vars end # A way to deal with compound make variables (that is, # make variables that get more than just themselves on the output) COMPOUND_MAKE_VARIABLES = { "CFLAGS" => "$(CFLAGS) $(ARCH_FLAG)", "DLDFLAGS" => "$(DLDFLAGS) $(ARCH_FLAG)", "LIBS" => "$(LIBRUBYARG) $(libs) $(LIBS)", } # Returns the contents of a "compound" variable. def compound_var_expand(var, value = nil, hash = MAKEFILE_CONFIG) if value.nil? value = MAKEFILE_CONFIG[var] end COMPOUND_MAKE_VARIABLES[var].gsub("$(#{var})",value) end # Returns a string containing all the configuration variables. We # use the MAKEFILE_CONFIG for more flexibility in the Makefile: the # variables can then be redefined on the make command-line. def output_config_variables str = "" keys = @@config_variables_used.uniq global_to_config new_keys = [] begin # we merge the new keys with the old keys += new_keys keys.uniq! new_keys = [] keys.each do |k| if MAKEFILE_CONFIG.key? k new_keys += subvars(MAKEFILE_CONFIG[k], MAKEFILE_CONFIG) end if COMPOUND_MAKE_VARIABLES.key? k new_keys += subvars(COMPOUND_MAKE_VARIABLES[k], MAKEFILE_CONFIG) end end end until (keys + new_keys).uniq.length == keys.length for var in keys.uniq.sort # We output the variable only if it is not empty: makes # it a lot easier to modify them from outside... if COMPOUND_MAKE_VARIABLES.key?(var) str += "#{var}=#{compound_var_expand(var)}\n" elsif MAKEFILE_CONFIG[var] =~ /\S/ str += "#{var}=#{MAKEFILE_CONFIG[var]}\n" end end return str end # This hash contains replacements for basic filesystem functions # based on ruby and FileUtils. They are used only if the corresponding # element is missing in CONFIG. They also contains some other default # values for possibly missing CONFIG keys. CONFIG_DEFAULTS = { # first, a short for the rest... "RB_FILE_UTILS" => "$(RUBY_INSTALL_NAME) -r fileutils", "INSTALL_SCRIPT" => "$(RB_FILE_UTILS) -e 'FileUtils.install(ARGV[0],ARGV[1],:mode => 0755)'", "INSTALL_DATA" => "$(RB_FILE_UTILS) -e 'FileUtils.install(ARGV[0],ARGV[1],:mode => 0644)'", "INSTALL_PROGRAM" => "$(INSTALL_SCRIPT) ", "MAKEDIRS" => "$(RB_FILE_UTILS) -e 'FileUtils.makedirs(ARGV)'", "RM" => "$(RB_FILE_UTILS) -e 'FileUtils.rm(ARGV)'", "RM_PATH" => # a trick to remove a path "$(RB_FILE_UTILS) -e 'd = ARGV[0]; begin ;while FileUtils.rmdir d, :verbose=>true; d = File.dirname(d);end; rescue ; end;'", "DEFINES" => "", "LIBARG" => "-l%s", "LIBS_SUP" => "", } # This functions checks for missing features in the CONFIG hash and # supplements them using the FILE_UTILS_COMMAND hash if necessary. def check_missing_features CONFIG.delete("INSTALL_SCRIPT") # Not consistent, it's better to use # the Fileutils stuff for key, val in CONFIG_DEFAULTS if CONFIG.key?(key) and CONFIG[key] =~ /\w/ # everything is fine else # we supplement the feature. MAKEFILE_CONFIG[key] = val end end end # Takes a list of CONFIG variables and returns them joined by spaces. def Mkmf2.config_join(*vars) return vars.collect { |v| Mkmf2.config_var(v) }.join(' ') end # Returns the way to represent the dependencies of a rule in the # Makefile. def Mkmf2.mf_deps return " $(@D)" end # Returns the way to represent the current target in the Makefile. def Mkmf2.mf_target return " $@" end # A small helper function to write quickly rules, based on a configuration # item. def Mkmf2.mf_rule(cfg,*args) if args.empty? return Mkmf2.config_var(cfg) + Mkmf2.mf_deps + Mkmf2.mf_target else return Mkmf2.config_var(cfg) + ' ' + args.join(' ') end end # This function returns the common build rules for C and C++ files. def Mkmf2.common_build_rules rules = [] for ext in Mkmf2.c_files_extensions rules << MfRule.new(".#{ext}.#{CONFIG["OBJEXT"]}", Mkmf2.config_join("CC", "CFLAGS", "CPPFLAGS", "INCLUDEDIRS", "DEFINES") + " -c $< #{CONFIG["OUTFLAG"]} $@" ) end # The same, but with C++: for ext in Mkmf2.cpp_files_extensions rules << MfRule.new(".#{ext}.#{CONFIG["OBJEXT"]}", Mkmf2.config_join("CXX", "CXXFLAGS", "CPPFLAGS", "INCLUDEDIRS", "DEFINES") + " -c $< #{CONFIG["OUTFLAG"]} $@" ) end # A simple rule for making directories: rules << MfRule.new("%/", Mkmf2.config_join("MAKEDIRS") + " $@") return rules end # Now, the infrastructure for dealing with include and library # directories: @@include_path = [Mkmf2.config_var("rubylibdir"), Mkmf2.config_var("archdir"), '.', File.join('.','include') ] # Adds one or more +paths+ to the current include path. def add_include_path(*paths) @@include_path += paths.flatten end def output_include_path return @@include_path.collect {|v| "-I#{v}" }.join(' ') end @@library_path = [] # Adds one or more +paths+ to the current library path. def add_library_path(*paths) @@library_path += paths.flatten end def output_library_path return @@library_path.collect {|v| "-L#{v}" }.join(' ') end # I know this is bad design, but that's the best I think of for now. # This function better be called before using MAKEFILE_CONFIG directly. def update_makefile_config global_to_config setup_paths_variables end # Sets up the various variables pertaining to include # and library paths. def setup_paths_variables MAKEFILE_CONFIG["INCLUDEDIRS"] = output_include_path MAKEFILE_CONFIG["LIBDIRS"] = output_library_path end # A recommandation for line size in the Makefile MAKEFILE_LINE_SIZE = 40 # Returns a string where all the elements are joined together. The lines # will break if they exceed +line_size+, but the elements themselves # will not be broken. They will be indented def Mkmf2.pretty_print_list(list, indent = 0, line_size = MAKEFILE_LINE_SIZE) if indent.is_a? String indent = indent.length end lines = [] for elem in list if lines.last.nil? || (lines.last + elem).length > line_size lines << elem.dup # necessary to force ruby to create else lines.last.concat(' ' + elem) end end return lines.join("\\\n#{' ' * indent}") end # Returns the string corresponding to a rule, given by the string +rule+, # which can take the following values: # +install_data+:: returns the rule for installing a data file to a # given place # +install_script+:: the rule for installing code at a given place # +remove+:: to remove files # +directory+:: to create a directory # +build_library+:: to build a library; in that case, the first argument # is the name of the target, and the ones after # the objects. # # the optional arguments are the arguments to the rule, if they exist. Else, # they default to $(@D) $@ (or something like that). def Mkmf2.rule(rule, *args) case rule when 'install_data' return Mkmf2.mf_rule("INSTALL_DATA",args) when 'install_script' return Mkmf2.mf_rule("INSTALL_SCRIPT",args) when 'install_bin' return Mkmf2.mf_rule("INSTALL_PROGRAM",args) when 'remove' return "-" + Mkmf2.mf_rule("RM",args) when 'remove_path' return "-" + Mkmf2.mf_rule("RM_PATH", args) when 'directory' # to create a directory return Mkmf2.mf_rule("MAKEDIRS", args) when 'build_library' return Mkmf2.mf_rule("LDSHARED", Mkmf2.config_join("DLDFLAGS", "LIBDIRS"), CONFIG["OUTFLAG"], "$@", # the target, args, # and the source # The libraries should better come in the end, # since for instance the standard linux ld # does only link with the symbols that have been # reported missing in the previous files. Mkmf2.config_join("LIBRUBYARG_SHARED", # we shouldn't forget this one !! "LOCAL_LIBS", "LIBS", # and then, the global libraries # that have been added using # have_library "LIBS_SUP") ) end end @@directories = {} # Register a directory that we might need to create. Make sure that # the rules don't appear twice. def Mkmf2.register_dir(dir) if ! @@directories.key?(dir) @@directories[dir] = true end end # Return the rules to create the necessary directories def directory_rules rules = [] for dir in @@directories.keys rules << MfRule.new(dir, Mkmf2.rule('directory',dir)) end return rules end # A small helper function to extract the install directory name # for one file. +file+ is the file, +install_dir+ where we want to # install it. This should take care of messy dots. def Mkmf2.dest_dir(file, install_dir) dir = File::dirname(file) if dir == "." return install_dir else return File.join(install_dir,dir) end end # A helper function for MfEntity instances that will have to install # files into directories. # +rule+:: the rule to use, see Mkmf2::rule # +install_path+:: the installation path # +files+:: a hash containing original -> destination pairs. def Mkmf2.install_files(rule, install_path,files) rules = [] files.each do |k,v| destdir = Mkmf2.dest_dir(v, install_path) Mkmf2.register_dir(destdir) rules.push(MfRule.new(File.join(install_path,v), Mkmf2::rule(rule, k, File.join(install_path,v)), [k,destdir])) # depends on both the file # that will be installed and the directory where to install it. end return rules end # The companion of install_files, returning a list of strings # rather than MfRules. Bascially behaves the same way. It is probably # a better thing to use this function now. def Mkmf2.install_files_rules(rule, install_path,files) rules = [] files.each do |k,v| dir = File::dirname(v) Mkmf2.register_dir(Mkmf2.dest_dir(v,install_path)) rules.push Mkmf2::rule(rule, k, File.join(install_path,v)) # depends on both the file # that will be installed and the directory where to install it. end return rules end # Small helper function for write_makefile. +target+ is # a hash containing a "deps" array and a "rules" array # which are created in case of need. def unwrap_rules(target, source) # we first make sure that the keys exist, even if we don't # have anything to append to them. if ! target.has_key? "deps" target["deps"] = [] end if ! target.has_key? "rules" target["rules"] = [] end return if source.empty? target["deps"] << source.shift target["rules"] += source end # Writes the Makefile using @@entities -- and others... def write_makefile(file_name = "Makefile") puts "Writing #{file_name}" install = {} uninstall = {} build = {} clean = {} vars = [] for entity in @@entities t = entity.install_rules unwrap_rules(install,t) t = entity.uninstall_rules unwrap_rules(uninstall,t) t = entity.build_rules unwrap_rules(build,t) t = entity.clean_rules unwrap_rules(clean,t) vars << entity.variables end # Common rules: common = Mkmf2.common_build_rules() # Setup the path variables: setup_paths_variables dir_rules = directory_rules() # OK, now, everything is prepared, we just need to create the # makefile and output everything into it... f = open(file_name, File::WRONLY|File::CREAT|File::TRUNC) # First, the variables in use: f.puts "# Configurations variables, from rbconfig.rb" f.puts output_config_variables # build has to be the first target so that simply # invoking make does the building, but not the installing. # now, the main rules f.puts "\n\n# main rules" # we force the dependence on build for install so that # ruby library files don't get installed before the c code is # compiled... # build is output first so that invoking make without # arguments builds. f.print MfRule.new("build", nil, build["deps"]).to_s f.print MfRule.new("install", nil, ["build"] + install["deps"]).to_s f.print MfRule.new("uninstall", nil, uninstall["deps"]).to_s f.print MfRule.new("clean", [Mkmf2.rule('remove',"**/*~")], # remove archive files # by default in the clean target. clean["deps"]).to_s # Add a distclean rule, to make debuild happy. f.print <<"EOR" distclean: clean \t@-$(RM) Makefile extconf.h conftest.* mkmf2.log \t@-$(RM) core ruby$(EXEEXT) *~ $(DISTCLEANFILES) EOR # Phony targets: f.puts ".PHONY: build install uninstall" f.puts "\n\n# Common rules:" common.each {|v| f.print v.to_s } # We write the variables: f.puts "\n\n# entities-dependent variables" for v in vars v.each do |k,v| f.print "#{k}=#{v}\n" end end f.puts "\n\n# Rules to make directories" # directory rules for rule in dir_rules f.print rule.to_s end f.puts "\n\n# entities-dependent rules" # We write the rules for rule in (install['rules'] + uninstall['rules'] + build['rules'] + clean['rules']) f.print rule.to_s end if @@user_rules.length > 0 f.puts "\n\n# User-defined rules" for rule in @@user_rules f.puts rule.to_s end end f.close # not necessary, but good practice ;-) ?? end # Adds a custom rule to the Makefile. If only the first argument # is specified, it is written as is to the Makefile. If at least the # second or the third is non-nil, a MfRule is created with the arguments # and written to the Makefile. def custom_rule(str, rule = nil, deps = nil) if rule || deps @@user_rules << MfRule.new(str, rule, deps) else @@user_rules << str end end # *The* compatibility function with the mkmf.rb ! def create_makefile(target) declare_library(File.dirname(target)) declare_binary_library(target, "**/*.c") write_makefile end # A function to ease the task of producing several libraries from the # same source tree. It sets up a library context for one directory, # adding it to the include path, and taking care of the files in: # +lib/+:: the ruby library files; # +include/+:: the include files # # +dir+ is the directory in the source, +target_dir+ the base directory # for target files. It is pretty rudimentary, but should do the job # for many cases (and especially Tioga ;-) !). +name+ is the name # of the binary library. If nil, doesn't try to build a binary library. # +include_dir+ is the target name for includes. If +nil+, doesn't # try to install them. # # You can use either the return value or a block to change somehow the # behavior of the different objects produced (like adding a binary library # for instance). def setup_dir(dir, target_dir, bin_name = nil, include_dir = nil) add_include_path(dir, File.join(dir, 'include')) lib = declare_library(target_dir, "#{dir}/lib/**/*.rb") binlib = declare_binary_library(bin_name, "#{dir}/**/*.c") if ! bin_name.nil? include = declare_includes(include_dir, "#{dir}/include/**/*.h") if ! include_dir.nil? yield lib, binlib, include if block_given? [lib, binlib, include] end # the module variable that will hold the command-line arguments: # even with a similar name, the contents of this variable will # be quite different from the old $configure_args @@configure_args = {} # Parses command-line arguments, wiping the ones that we understood. def parse_cmdline $*.delete_if do |arg| if arg =~ /^--(.*)/ # does look like a command-line argument... case $1 when "local" # installation to sitedir Mkmf2.set_model("local") true when "dist" Mkmf2.set_model("dist") true when "home" Mkmf2.set_model("home") true when /^--with(.*)/ # this is my understanding of the # with-config stuff. It is a complete rewrite, I don't like # much the old code rhs = $1 # right-hand side case rhs when /-([\w-]+)=(.*)/ @@configure_args[$1] = $2 when /out-([\w-]+)$/ @@configure_args[$1] = false when /-([\w-]+)$/ @@configure_args[$1] = true else raise "Argument #{arg} not understood" end else false end else false end end end # This function is a try at reproducing the functionnality of the # old mkmf.rb's dir_config. It is not a copy, and might not work # well in all situations. Basically, if --with-target-dir has been # specified, use dir/include dir/lib. If --with-target-(include|lib) # have been specified, use them ! def dir_config(target) ldir = nil idir = nil if @@configure_args.key?("#{target}-lib") ldir = @@configure_args["#{target}-lib"] elsif @@configure_args.key?("#{target}-dir") ldir = File::join(@@configure_args["#{target}-dir"], "lib") end if ldir add_library_path(ldir.split(File::PATH_SEPARATOR)) end if @@configure_args.key?("#{target}-include") idir = @@configure_args["#{target}-include"] elsif @@configure_args.key?("#{target}-dir") idir = File::join(@@configure_args["#{target}-dir"], "include") end if idir add_include_path(idir.split(File::PATH_SEPARATOR)) end return [idir, ldir] end ########################################################################### # This module is copied verbatim from the old mkmf.rb code. It comes dead # # useful for logging things. I hope it will not cause licence conflicts. # ########################################################################### module Logging @log = nil @logfile = 'mkmf2.log' @orgerr = $stderr.dup @orgout = $stdout.dup @postpone = 0 def self::open @log ||= File::open(@logfile, 'w') @log.sync = true $stderr.reopen(@log) $stdout.reopen(@log) yield ensure $stderr.reopen(@orgerr) $stdout.reopen(@orgout) end def self::message(*s) @log ||= File::open(@logfile, 'w') @log.sync = true @log.printf(*s) end def self::logfile file @logfile = file if @log and not @log.closed? @log.flush @log.close @log = nil end end def self::postpone tmplog = "mkmftmp#{@postpone += 1}.log" open do log, *save = @log, @logfile, @orgout, @orgerr @log, @logfile, @orgout, @orgerr = nil, tmplog, log, log begin log.print(open {yield}) @log.close File::open(tmplog) {|t| FileUtils.copy_stream(t, log)} ensure @log, @logfile, @orgout, @orgerr = log, *save @postpone -= 1 rm_f tmplog end end end end # Also from old mkmf.rb CONFTEST_C = "conftest.c" def xsystem(command) Logging::open do puts command.quote system(command) end end # Also from old mkmf.rb def log_src(src) Logging::message <<"EOM", src checked program was: /* begin */ %s/* end */ EOM end # Also from old mkmf.rb def create_tmpsrc(src) src = yield(src) if block_given? src = src.sub(/[^\n]\z/, "\\&\n") open(CONFTEST_C, "wb") do |cfile| cfile.print src end src end # Also from old mkmf.rb def try_do(src, command, &b) src = create_tmpsrc(src, &b) xsystem(command) ensure log_src(src) end # Also from old mkmf.rb def checking_for(m, fmt = nil) f = caller[0][/in `(.*)'$/, 1] and f << ": " #` for vim m = "checking for #{m}... " print m a = r = nil Logging::postpone do r = yield a = (fmt ? fmt % r : r ? "yes" : "no") << "\n" "#{f}#{m}-------------------- #{a}\n" end puts a $stdout.flush Logging::message "--------------------\n\n" r end # also taken straight from mkmf.rb def rm_f(*files) FileUtils.rm_f(Dir[files.join("\0")]) end # A small wrapper around Config::expand which diminishes the size of the code # and makes sure the MAKEFILE_CONFIG hash is updated. def expand_vars(str) update_makefile_config string = Config::expand(str,MAKEFILE_CONFIG) # then, we need to turn all the remaining $(THING) into $THING, so that # the shell doesn't spawn subshells ? return string.gsub(/\$\((\w+)\)/) { "$#$1" } end # Add defines to the build def add_define(d) MAKEFILE_CONFIG["DEFINES"] += " -D_#{d}" end # This is a compatibility function with the previous mkmf.rb. It does check # for the presence of a header in the current include directories. If found, # it returns true and sets the define HAVE_... def have_header(header, &b) checking_for header do if try_do("#include <#{header}>", expand_vars("$(CPP) $(INCLUDEDIRS) " \ "$(CPPFLAGS) $(CFLAGS) $(DEFINES) "\ "#{CONFTEST_C} $(CPPOUTFILE)"),&b) add_define("HAVE_#{header.sanitize}") true else false end end ensure rm_f("conftest*") end # The same as have_header, but fails if the header is not found... def require_header(header, message = nil ,&b) if ! have_header(header,&b) if message puts message end raise "Header #{header} not found, stopping\n" end end def mkmf2_init check_missing_features parse_cmdline setup_model config_to_global end def headers(header) headers = "" if header if header.is_a?(Array) header.each {|h| headers += "#include <#{h.to_s}>\n" } else headers += "#include <#{header.to_s}>\n" end end return headers end # Tries to link the given code with the extra flags given def try_link(code, extras = "") return try_do(code, expand_vars("$(CC) $(OUTFLAG)conftest $(INCFLAGS) " + "#{CONFTEST_C} " + " -I$(hdrdir) $(CPPFLAGS) $(CFLAGS) $(src)" + " $(LIBPATH) $(LDFLAGS) $(ARCH_FLAG) " + " $(LOCAL_LIBS) $(LIBS) $(LIBS_SUP) #{extras}" ) ) ensure rm_f("conftest*") end def try_func(func, extra, h) headers = headers(h) try_link(<<"SRC", extra) or try_link(<<"SRC", extra) #{headers} /*top*/ int main() { return 0; } int t() { #{func}(); return 0; } SRC #{headers} /*top*/ int main() { return 0; } int t() { void ((*volatile p)()); p = (void ((*)()))#{func}; return 0; } SRC end # Compatibility function from mkmf.rb. Checks if the compiler # can find the given function in the given library. If the function # is not given, we look for main but it's definitely not a good idea. # +header+ is a header that can be included to get the prototype for # this function. It can possibly be an array, in which case it is # interpreted as a list of headers that should be included. def have_library(lib, func = nil, header=nil, &b) if func.nil? func = "main" end libarg = "#{MAKEFILE_CONFIG["LIBARG"]%lib}" checking_for "#{func}() in #{libarg}" do if try_func(func, libarg, header) add_define("HAVE_#{lib.sanitize}") MAKEFILE_CONFIG["LIBS_SUP"] += " #{libarg}" true else false end end end # Returns true if a function could be found def have_func(func, header = nil) checking_for "#{func}() " do if try_func(func, "", header) add_define("HAVE_#{func.sanitize}") true else false end end end end include Mkmf2 mkmf2_init