diff --git a/.autotest b/.autotest new file mode 100644 index 0000000..ae8d4b7 --- /dev/null +++ b/.autotest @@ -0,0 +1,23 @@ +# -*- ruby -*- + +require 'autotest/restart' + +# Autotest.add_hook :initialize do |at| +# at.extra_files << "../some/external/dependency.rb" +# +# at.libs << ":../some/external" +# +# at.add_exception 'vendor' +# +# at.add_mapping(/dependency.rb/) do |f, _| +# at.files_matching(/test_.*rb$/) +# end +# +# %w(TestA TestB).each do |klass| +# at.extra_class_map[klass] = "test/test_misc.rb" +# end +# end + +# Autotest.add_hook :run_command do |at| +# system "rake build" +# end diff --git a/History.rdoc b/History.rdoc new file mode 100644 index 0000000..950554e --- /dev/null +++ b/History.rdoc @@ -0,0 +1,6 @@ +=== 1.0.0 / 2012-05-30 + +* 1 major enhancement + + * Birthday! + diff --git a/Manifest.txt b/Manifest.txt new file mode 100644 index 0000000..c7fe44a --- /dev/null +++ b/Manifest.txt @@ -0,0 +1,9 @@ +.autotest +History.rdoc +Manifest.txt +README.rdoc +Rakefile +lib/rubygems_plugin.rb +lib/rubygems/commands/exefy_command.rb +lib/templates/gem_exe.c +test/test_gem_exefy.rb diff --git a/README.rdoc b/README.rdoc new file mode 100644 index 0000000..0ed9ba6 --- /dev/null +++ b/README.rdoc @@ -0,0 +1,74 @@ += gem-exefy + +code :: http://github.com/bosko/gem-exefy +bugs :: http://github.com/bosko/gem-exefy/issues + +== DESCRIPTION: + +GemExefy is RubyGems plugin aimed to replace batch files (.bat) with executables with same name. + +Rationale behind this is described in this message https://groups.google.com/forum/?fromgroups#!topic/rubyinstaller/fQCuPfiuuRc + +GemExefy searches following directories for batch files + +- Gem.bindir +- Gem.path + +Since Gem::path array contains directories pointing to the top level directory and executables are saved in the Gem#bindir folder, GemExefy concatenates directory from Gem.path with value of Gem#bindir (bin directory defined in the target Gem) and name from Gem#executables array with .bat extension. If such file is found it is replaced with corresponding executable file with same name and .exe extension. + +Backup of old batch files will be made if '-b' option is used. Otherwise they will be deleted. + +== FEATURES/PROBLEMS: + +* No known problems at the moment + +== SYNOPSIS: + + C:\> gem exefy + +or: + + C:\> gem exefy -b + +== REQUIREMENTS: + +* RubyInstaller Ruby version +* RubyInstaller's DevKit + +== INSTALL: + +* gem install gem-exefy + +== DEVELOPERS: + +After checking out the source, run: + + $ rake newb + +This task will install any missing dependencies, run the tests/specs, +and generate the RDoc. + +== LICENSE: + +(The MIT License) + +Copyright (c) 2012 FIX + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..e859c4e --- /dev/null +++ b/Rakefile @@ -0,0 +1,23 @@ +# coding: utf-8 +# -*- ruby -*- + +require 'rubygems' +require 'hoe' + +Hoe.spec 'gem-exefy' do + developer('Boško Ivanišević', 'bosko.ivanisevic@gmail.com') + + self.urls = {"GitHub repository" => "http://github.com/bosko/gem-exefy"} + self.readme_file = 'README.rdoc' + self.history_file = 'History.rdoc' + self.extra_rdoc_files = FileList['*.rdoc'] + self.require_rubygems_version(">= 1.8.0") + self.version = "0.0.1" + self.post_install_message = %Q{************************************************** + + Thank you for installing #{self.name}-#{self.version}! + +**************************************************} +end + +# vim: syntax=ruby diff --git a/lib/rubygems/commands/exefy_command.rb b/lib/rubygems/commands/exefy_command.rb new file mode 100644 index 0000000..d438725 --- /dev/null +++ b/lib/rubygems/commands/exefy_command.rb @@ -0,0 +1,100 @@ +require "rubygems/command" +require "tmpdir" + +module Gem + module Commands + class ExefyCommand < Gem::Command + def initialize + super 'exefy', "Replaces Gem's batch file with executable file (Windows only)" + add_option('-b', '--backup-batch-files', + 'Keep backup of old batch files') do |value, options| + options[:backup_batch_files] = value + end + end + + def execute + unless RUBY_PLATFORM =~ /mingw/ + say "This command can be executed only on Windows OS" + return + end + + get_all_gem_names.each do |name| + begin + cur_gem = Gem::Specification.find_by_name(name) + batch_files(cur_gem).each do |k,v| + generate_exe(k, v) + end + rescue Gem::LoadError => e + say "Cannot exefy. Gem #{name} not found" + end + end + end + + def batch_files(gem) + bf = {} + test_paths = Gem.path.map {|gp| File.join(gp, gem.bindir)}.unshift(Gem.bindir) + gem.executables.each do |executable| + test_paths.map {|tp| File.join(tp, "#{executable}.bat")}.each do |bat| + bf[executable] = [] unless bf[executable] + bf[executable] << bat if File.exist?(bat) + end + end + bf + end + + def generate_exe(executable, batch_files) + log_message "Generating exe file" + + exefier_src_path = File.join(File.expand_path("../../../templates", __FILE__), "gem_exe.c") + Dir.mktmpdir do |build_dir| + # First create executable for all batch files (it is same .exe file but + # batch files are found in different directories). + targets = Hash[*['c', 'o', 'exe'].map { |ext| [ext, File.join(build_dir, "#{executable}.#{ext}")]}.flatten] + FileUtils.cp exefier_src_path, targets["c"] + compile(targets["c"], targets["o"]) + link(targets["o"], targets["exe"]) + + batch_files.each do |bf| + log_message "Copying exe file to #{File.dirname(bf)}..." + FileUtils.cp targets["exe"], File.dirname(bf) + if options[:backup_batch_files] + log_message "Creating backup of old batch file" + File.rename(bf, "#{bf}.bcp") + else + File.rm bf + end + end + end + end + + def compile(source, target) + cflags = RbConfig::CONFIG["CFLAGS"] + cppflags = RbConfig::CONFIG["CPPFLAGS"] + + hdr_dir = RbConfig::CONFIG["rubyhdrdir"] + arch_dir = RbConfig::CONFIG["arch"] + + include_dirs = "-I#{hdr_dir}/#{arch_dir} -I#{hdr_dir}" + + cc = ENV.fetch("CC", RbConfig::CONFIG["CC"]) + + system "#{cc} -c #{source} -o #{target} #{cflags} #{cppflags} #{include_dirs}" + end + + def link(objs, target) + libruby_dir = RbConfig::CONFIG["libdir"] + libruby = RbConfig::CONFIG["LIBRUBYARG"] + + libs = RbConfig::CONFIG["LIBS"] + libs_dir = "-L#{libruby_dir} #{libruby} #{libs}" + + cc = ENV.fetch("CC", RbConfig::CONFIG["CC"]) + system "#{cc} #{objs} -o #{target} #{libs_dir}" + end + + def log_message(message) + say message if Gem.configuration.really_verbose + end + end + end +end diff --git a/lib/rubygems_plugin.rb b/lib/rubygems_plugin.rb new file mode 100644 index 0000000..832f083 --- /dev/null +++ b/lib/rubygems_plugin.rb @@ -0,0 +1,3 @@ +require "rubygems/command_manager" + +Gem::CommandManager.instance.register_command :exefy \ No newline at end of file diff --git a/lib/templates/gem_exe.c b/lib/templates/gem_exe.c new file mode 100644 index 0000000..b59e91e --- /dev/null +++ b/lib/templates/gem_exe.c @@ -0,0 +1,78 @@ +#include "ruby.h" +#include + +#ifdef HAVE_LOCALE_H +#include +#endif + +#ifndef MAXPATHLEN +# define MAXPATHLEN 1024 +#endif + +void +dump_args(int argc, char **argv) +{ + int i; + printf("Number of arguments: %d\n", argc); + + for (i = 0; i < argc; ++i) + printf("Argument %d: %s\n", i, argv[i]); + + printf("=================\n"); +} + +int +main(int argc, char **argv) +{ +#ifdef HAVE_LOCALE_H + setlocale(LC_CTYPE, ""); +#endif + + int i; + int myargc; + char** myargv; + char script_path[MAXPATHLEN]; + char* dump_val; + + dump_val = getenv("EXEFY_DUMP"); + + if (GetModuleFileName(NULL, script_path, MAXPATHLEN)) { + for (i = strlen(script_path) - 1; i >= 0; --i) { + if (*(script_path + i) == '.') { + *(script_path + i) = '\0'; + break; + } + } + + DWORD attr = GetFileAttributes(script_path); + if (attr == INVALID_FILE_ATTRIBUTES) { + printf("Script %s is missing!", script_path); + return -1; + } + // Let Ruby initialize program arguments + ruby_sysinit(&argc, &argv); + + // Change arguments by inserting path to script file + // as second argument (first argument is always executable + // name) and copying arguments from command line after it. + myargc = argc + 1; + myargv = (char**)xmalloc(sizeof(char*) * (myargc + 1)); + memset(myargv, 0, sizeof(char*) * (myargc + 1)); + *myargv = *argv; + *(myargv + 1) = &script_path[0]; + + for (i = 1; i < argc; ++i) { + *(myargv + i + 1) = *(argv + i); + } + + if (NULL != dump_val) { + dump_args(myargc, myargv); + } + + { + RUBY_INIT_STACK; + ruby_init(); + return ruby_run_node(ruby_options(myargc, myargv)); + } + } +}