diff --git a/Rakefile b/Rakefile index 2a8e11d43..ef7d536c6 100644 --- a/Rakefile +++ b/Rakefile @@ -31,6 +31,7 @@ load "#{MRUBY_ROOT}/tasks/presym.rake" load "#{MRUBY_ROOT}/tasks/test.rake" load "#{MRUBY_ROOT}/tasks/benchmark.rake" load "#{MRUBY_ROOT}/tasks/doc.rake" +load "#{MRUBY_ROOT}/tasks/install.rake" ############################## # generic build targets, rules @@ -67,34 +68,6 @@ task :deep_clean => %w[clean doc:clean] do puts "Cleaned up mrbgems build folder" end -PREFIX = ENV['PREFIX'] || ENV['INSTALL_PREFIX'] || '/usr/local' - -desc "install compiled products" -task :install => :install_bin do - if host = MRuby.targets['host'] - install_D host.libmruby_static, File.join(PREFIX, "lib", File.basename(host.libmruby_static)) - # install mruby.h and mrbconf.h - Dir.glob(File.join(MRUBY_ROOT, "include", "*.h")) do |src| - install_D src, File.join(PREFIX, "include", File.basename(src)) - end - Dir.glob(File.join(MRUBY_ROOT, "include", "mruby", "*.h")) do |src| - install_D src, File.join(PREFIX, "include", "mruby", File.basename(src)) - end - Dir.glob(File.join(File.join(MRUBY_ROOT, "build", "host", "include", "mruby", "presym", "*.h"))) do |src| - install_D src, File.join(PREFIX, "include", "mruby", "presym", File.basename(src)) - end - end -end - -desc "install compiled executable (on host)" -task :install_bin => :all do - if host = MRuby.targets['host'] - Dir.glob(File.join(MRUBY_ROOT, "bin", "*")) do |src| - install_D src, File.join(PREFIX, "bin", File.basename(src)) - end - end -end - desc "run all pre-commit hooks against all files" task :check do sh "pre-commit run --all-files" diff --git a/doc/guides/compile.md b/doc/guides/compile.md index 4302d1362..08dedbf9d 100644 --- a/doc/guides/compile.md +++ b/doc/guides/compile.md @@ -557,6 +557,56 @@ LDFLAGS = `$(MRB_CONFIG) --ldflags` LIBS = `$(MRB_CONFIG) --libs` ``` +## Install + +To install the files in the `bin`, `include` and `lib` directories generated by the "host" build target into a system directory, do the following: + +```console +$ rake install +``` + +If there are multiple build targets in the build configuration file, to install the products of all build targets, do the following: + +```console +$ rake install:full +``` + +To install only one of several build targets, e.g., the "its-mine" build target, do the following: + +```console +$ rake install:full:its-mine +``` + +To install only the executable files, do the following: + +```console +$ rake install_bin # only "host" build target +$ rake install:bin # all build targets +$ rake install:bin:its-mine # only "its-mine" build target +``` + +### Installation Directory + +The installation directory is `/usr/local` for the "host" build target and `/usr/local/mruby/` for the others. +To change them, you can set the environment variable `PREFIX` or use `MRuby::Build#install_prefix = dir` in your build configuration file. + +The `PREFIX` environment variable affects all build targets and changes the `/usr/local` part. + +The `MRuby::Build#install_prefix` can be set for each individual build target. +In this case, the environment variable `PREFIX` is ignored. + +Also, if the environment variable `DESTDIR` is set, it will prepend to the path obtained by `install_prefix` to determine the final write directory. +This is intended for temporary file expansion by the user's package work. + +--- + +To summarize: + +- The default value of the environment variable `PREFIX` is `/usr/local`. +- For the "host" build target, the default value of `MRuby::Build#install_prefix` is ``. +- For a build target other than "host", the default value of `MRuby::Build#install_prefix` is `/mruby/`. +- If the environment variable `DESTDIR` is set, the actual write directory is `/`. + ## Tips - If you see compilation troubles, try `rake clean` first. diff --git a/lib/mruby/build.rb b/lib/mruby/build.rb index 4925cee33..3864e6c97 100644 --- a/lib/mruby/build.rb +++ b/lib/mruby/build.rb @@ -1,12 +1,16 @@ require "mruby/core_ext" require "mruby/build/load_gems" require "mruby/build/command" +autoload :Find, "find" module MRuby autoload :Gem, "mruby/gem" autoload :Lockfile, "mruby/lockfile" autoload :Presym, "mruby/presym" + INSTALL_PREFIX = ENV['PREFIX'] || ENV['INSTALL_PREFIX'] || '/usr/local' + INSTALL_DESTDIR = ENV['DESTDIR'] || '' + class << self def targets @targets ||= {} @@ -97,6 +101,7 @@ module MRuby @file_separator = '/' @build_dir = "#{build_dir}/#{@name}" @gem_clone_dir = "#{build_dir}/repos/#{@name}" + @install_prefix = nil @defines = [] @cc = Command::Compiler.new(self, %w(.c), label: "CC") @cxx = Command::Compiler.new(self, %w(.cc .cxx .cpp), label: "CXX") @@ -370,14 +375,35 @@ EOS end end - def define_installer(src) - dst = "#{self.class.install_dir}/#{File.basename(src)}" + def define_installer_outline(src, dst) file dst => src do - install_D src, dst + _pp "GEN", src.relative_path, dst.relative_path + mkdir_p(File.dirname(dst)) + yield dst end dst end + if ENV['OS'] == 'Windows_NT' + def define_installer(src) + dst = "#{self.class.install_dir}/#{File.basename(src)}".pathmap("%X.bat") + define_installer_outline(src, dst) do + File.write dst, <<~BATCHFILE + @echo off + call "#{src}" %* + BATCHFILE + end + end + else + def define_installer(src) + dst = "#{self.class.install_dir}/#{File.basename(src)}" + define_installer_outline(src, dst) do + File.unlink(dst) if File.exist?(dst) + File.symlink(src, dst) + end + end + end + def define_installer_if_needed(bin) exe = exefile("#{build_dir}/bin/#{bin}") host? ? define_installer(exe) : exe @@ -485,6 +511,29 @@ EOS @internal end + def each_header_files(&block) + return to_enum(__method__) unless block + + basedir = File.join(MRUBY_ROOT, "include") + Find.find(basedir) do |d| + next unless File.file? d + yield d + end + + @gems.each { |g| g.each_header_files(&block) } + + self + end + + def install_prefix + @install_prefix || (self.name == "host" ? MRuby::INSTALL_PREFIX : + File.join(MRuby::INSTALL_PREFIX, "mruby/#{self.name}")) + end + + def install_prefix=(dir) + @install_prefix = dir&.to_s + end + protected attr_writer :presym diff --git a/lib/mruby/core_ext.rb b/lib/mruby/core_ext.rb index 1ad528c26..ac2737643 100644 --- a/lib/mruby/core_ext.rb +++ b/lib/mruby/core_ext.rb @@ -22,6 +22,27 @@ class String def remove_leading_parents Pathname.new(".#{Pathname.new("/#{self}").cleanpath}").cleanpath.to_s end + + def replace_prefix_by(dirmap) + [self].replace_prefix_by(dirmap)[0] + end +end + +class Array + # Replace the prefix of each string that is a file path that contains in its own array. + # + # dirmap is a hash whose elements are `{ "path/to/old-prefix" => "path/to/new-prefix", ... }`. + # If it does not match any element of dirmap, the file path is not replaced. + def replace_prefix_by(dirmap) + dirmap = dirmap.map { |older, newer| [File.join(older, "/"), File.join(newer, "/")] } + dirmap.sort! + dirmap.reverse! + self.flatten.map do |e| + map = dirmap.find { |older, newer| e.start_with?(older) } + e = e.sub(map[0], map[1]) if map + e + end + end end def install_D(src, dst) diff --git a/lib/mruby/gem.rb b/lib/mruby/gem.rb index 8fc1aeaaf..e228beb3e 100644 --- a/lib/mruby/gem.rb +++ b/lib/mruby/gem.rb @@ -284,6 +284,19 @@ module MRuby end end.all? end + + def each_header_files(&block) + return to_enum(__method__) unless block + + self.export_include_paths.flatten.uniq.compact.each do |dir| + Find.find(dir) do |d| + next unless File.file? d + yield d + end + end + + self + end end # Specification class Version diff --git a/mrbgems/mruby-bin-config/mrbgem.rake b/mrbgems/mruby-bin-config/mrbgem.rake index b4c437640..8a484d69c 100644 --- a/mrbgems/mruby-bin-config/mrbgem.rake +++ b/mrbgems/mruby-bin-config/mrbgem.rake @@ -11,7 +11,16 @@ MRuby::Gem::Specification.new('mruby-bin-config') do |spec| else mruby_config_dir = "#{build.build_dir}/bin" end - mruby_config = name + (ENV['OS'] == 'Windows_NT' ? '.bat' : '') + + if ENV['OS'] == 'Windows_NT' + suffix = '.bat' + refvar = '%\\1%' + else + suffix = '' + refvar = '${\\1}' + end + + mruby_config = name + suffix mruby_config_path = "#{mruby_config_dir}/#{mruby_config}" make_cfg = "#{build.build_dir}/lib/libmruby.flags.mak" tmplt_path = "#{__dir__}/#{mruby_config}" @@ -24,11 +33,16 @@ MRuby::Gem::Specification.new('mruby-bin-config') do |spec| directory mruby_config_dir - file mruby_config_path => [mruby_config_dir, make_cfg, tmplt_path] do |t| + file mruby_config_path => [__FILE__, mruby_config_dir, make_cfg, tmplt_path] do |t| config = Hash[File.readlines(make_cfg).map!(&:chomp).map! {|l| + l.gsub!(/\$\((\w+)\)/, refvar) l.gsub('\\"', '"').split(' = ', 2).map! {|s| s.sub(/^(?=.)/, 'echo ')} }] tmplt = File.read(tmplt_path) + tmplt.sub!(%r((?<=\A#!/bin/sh\n\n)), <<~SETDIR) + MRUBY_PACKAGE_DIR=$(dirname "$(dirname "$(readlink -f "$0")")") + + SETDIR File.write(t.name, tmplt.gsub(/(#{Regexp.union(*config.keys)})\b/, config)) chmod(0755, t.name) end diff --git a/mrbgems/mruby-bin-config/mruby-config.bat b/mrbgems/mruby-bin-config/mruby-config.bat index 949fea06f..8d8a589fe 100644 --- a/mrbgems/mruby-bin-config/mruby-config.bat +++ b/mrbgems/mruby-bin-config/mruby-config.bat @@ -1,5 +1,17 @@ @echo off +call :init "%~dp0." +goto top + +:init +rem call init2 for treatments last directory separator +call :init2 "%~dp1." +goto :eof + +:init2 +set MRUBY_PACKAGE_DIR=%~f1 +goto :eof + :top shift if "%0" equ "" goto :eof diff --git a/tasks/install.rake b/tasks/install.rake new file mode 100644 index 000000000..234bec8d8 --- /dev/null +++ b/tasks/install.rake @@ -0,0 +1,33 @@ +desc "install compiled products (on host)" +task :install => "install:full:host" + +desc "install compiled executable (on host)" +task :install_bin => "install:bin:host" + +desc "install compiled products (all build targets)" +task "install:full" + +desc "install compiled executable (all build targets)" +task "install:bin" + +MRuby.each_target do |build| + next if build.internal? + + prefix = File.join(MRuby::INSTALL_DESTDIR, build.install_prefix) + + task "install:full" => "install:full:#{build.name}" + + task "install:full:#{build.name}" => "install:bin:#{build.name}" do + Dir.glob(File.join(build.build_dir.gsub(/[\[\{\*\?]/, "\\\0"), "{include,lib}/**/*")) do |path| + install_D path, File.join(prefix, path.relative_path_from(build.build_dir)) if File.file? path + end + end + + task "install:bin" => "install:bin:#{build.name}" + + task "install:bin:#{build.name}" => "all" do + Dir.glob(File.join(build.build_dir.gsub(/[\[\{\*\?]/, "\\\0"), "{bin,host-bin}/**/*")) do |path| + install_D path, File.join(prefix, path.relative_path_from(build.build_dir)) if File.file? path + end + end +end diff --git a/tasks/libmruby.rake b/tasks/libmruby.rake index 1fb3cbc31..cc25aa047 100644 --- a/tasks/libmruby.rake +++ b/tasks/libmruby.rake @@ -8,22 +8,71 @@ MRuby.each_target do next unless libmruby_enabled? file libmruby_static => libmruby_objs.flatten do |t| + Rake::Task["expose_header_files"].invoke archiver.run t.name, t.prerequisites end + task "expose_header_files" do |t| + # Since header files may be generated dynamically and it is hard to know all of them, + # the task is executed depending on when libmruby.a is generated. + + gemsbasedir = File.join(build_dir, "include/mruby/gems") + dirmap = { + MRUBY_ROOT => build_dir + } + gems.each { |g| + dirmap[g.dir] = File.join(gemsbasedir, g.name) + dirmap[g.build_dir] = File.join(gemsbasedir, g.name) + } + + dirs = each_header_files.to_a + dirs.uniq! + dirs.replace_prefix_by(dirmap).zip(dirs).each do |dest, src| + if File.mtime(src).to_i > (File.mtime(dest).to_i rescue 0) + mkpath File.dirname(dest) + cp src, dest + end + end + end + file "#{build_dir}/lib/libmruby.flags.mak" => [__FILE__, libmruby_static] do |t| mkdir_p File.dirname t.name open(t.name, 'w') do |f| - gemincs = gems.map { |g| g.export_include_paths.map { |n| g.filename(n) } }.flatten.uniq - f.puts "MRUBY_CFLAGS = #{cc.all_flags([], gemincs)}" + f.puts <<~FLAGS_MAKE + # GNU make is required to use this file. + MRUBY_PACKAGE_DIR_GNU := $(shell dirname "$(firstword $(MAKEFILE_LIST))") + MRUBY_PACKAGE_DIR != dirname "$(MRUBY_PACKAGE_DIR_GNU)" + FLAGS_MAKE + + [ + [cc, "MRUBY_CC", "MRUBY_CFLAGS"], + [cxx, "MRUBY_CXX", "MRUBY_CXXFLAGS"], + [asm, "MRUBY_AS", "MRUBY_ASFLAGS"], + [objc, "MRUBY_OBJC", "MRUBY_OBJCFLAGS"] + ].each do |cc, cmd, flags| + incpaths = cc.include_paths.dup + dirmaps = { + MRUBY_ROOT => "$(MRUBY_PACKAGE_DIR)", + build_dir => "$(MRUBY_PACKAGE_DIR)" + } + gems.each do |g| + incpaths.concat g.export_include_paths + dirmaps[g.dir] = "$(MRUBY_PACKAGE_DIR)/include/mruby/gems/#{g.name}" + dirmaps[g.build_dir] = "$(MRUBY_PACKAGE_DIR)/include/mruby/gems/#{g.name}" + end + modcc = cc.clone + modcc.include_paths = incpaths.replace_prefix_by(dirmaps).uniq + + f.puts "#{cmd} = #{cc.command}" + f.puts "#{flags} = #{modcc.all_flags}" + end - f.puts "MRUBY_CC = #{cc.command}" f.puts "MRUBY_LD = #{linker.command}" libgems = gems.reject{|g| g.bin?} gem_flags = libgems.map {|g| g.linker.flags } gem_library_paths = libgems.map {|g| g.linker.library_paths } - f.puts "MRUBY_LDFLAGS = #{linker.all_flags(gem_library_paths, gem_flags)} #{linker.option_library_path % "#{build_dir}/lib"}" + f.puts "MRUBY_LDFLAGS = #{linker.all_flags(gem_library_paths, gem_flags)} #{linker.option_library_path % "$(MRUBY_PACKAGE_DIR)/lib"}" gem_flags_before_libraries = libgems.map {|g| g.linker.flags_before_libraries } f.puts "MRUBY_LDFLAGS_BEFORE_LIBS = #{[linker.flags_before_libraries, gem_flags_before_libraries].flatten.join(' ')}" @@ -31,7 +80,7 @@ MRuby.each_target do gem_libraries = libgems.map {|g| g.linker.libraries } f.puts "MRUBY_LIBS = #{linker.option_library % 'mruby'} #{linker.library_flags(gem_libraries)}" - f.puts "MRUBY_LIBMRUBY_PATH = #{libmruby_static}" + f.puts "MRUBY_LIBMRUBY_PATH = #{libmruby_static.replace_prefix_by(build_dir => "$(MRUBY_PACKAGE_DIR)")}" end end