# # Author: James M. Lawrence <quixoticsycophant@gmail.com>. # #Updated by Brett Bates and Jennifer Bottom. # The gems we need require 'net/ftp' require 'rbconfig' require 'ostruct' require 'fileutils' require 'optparse' require 'pathname' require 'rubygems' class Installer include FileUtils RbConfig = RbConfig::CONFIG BIT64 = (1.size == 8) #We use this to find the version of P4Ruby to get RB_BASENAME = Pathname.new "P4.rb" SO_BASENAME = Pathname.new "P4.#{RbConfig['DLEXT']}" #The files we download RAW_INSTALL_FILES = [ Pathname.new(RbConfig["sitelibdir"]) + RB_BASENAME, Pathname.new(RbConfig["sitearchdir"]) + SO_BASENAME, ] GEM_INSTALL_FILES = [ Pathname.new("lib") + RB_BASENAME, Pathname.new("ext") + SO_BASENAME, ] #The connection to the FTP server SERVER = "ftp.perforce.com" SERVER_TOP_DIR = Pathname.new "perforce" #Hard coded values for release dirs that don't contain P4Ruby HOSED_VERSIONS = %w[09.3 11.1] #Stores the path name to the API (that we use to get it from the FTP site) P4API_REMOTE_BASENAME = Pathname.new "p4api.tgz" #Same for getting P4Ruby P4RUBY_REMOTE_BASENAME = Pathname.new "p4ruby.tgz" #Where we build the stuff after we download it WORK_DIR = Pathname.new "work" DISTFILES_DIR = WORK_DIR + "distfiles" BUILD_DIR = WORK_DIR + "build" def parse_command_line OptionParser.new("Usage: ruby install.rb [options]", 24, "") { |parser| parser.on( "--version NN.N", "Version to download, e.g. 08.1. Default finds latest.") { |version| @s.version = version } parser.on( "--list-versions", "List available versions.") { @s.list_versions = true } parser.on( "--platform PLATFORM", "Perforce-named platform to download. Default guesses.") { |platform| @s.platform = platform } parser.on( "--list-platforms", "List available platforms for the given version.") { @s.list_platforms = true } parser.on( "--gem", "Gem configuration (for the gem installer).") { @s.gem_config = true } parser.on( "--uninstall", "Uninstall.") { @s.uninstall = true } parser.on( "--local", "Use the files in work/distfiles (manual download).") { @s.local = true } parser.parse(ARGV) } end def run @s = LazyStruct.new parse_command_line config if @s.uninstall uninstall elsif @s.list_platforms puts platforms elsif @s.list_versions puts versions elsif @s.platform.nil? platform_fail elsif @s.platform =~ %r!\Ant! windows_install else fetch build install verify_install end end def config if RbConfig["LIBRUBYARG_SHARED"].empty? raise "error: ruby must be configured with --enable-shared" end #Sets up the connection to the Perforce FTP server @s.ftp = Net::FTP.new(SERVER).tap { |t| t.passive = true t.login } @s.p4api = LazyStruct.new.tap { |t| t.basename = P4API_REMOTE_BASENAME } @s.p4ruby = LazyStruct.new.tap { |t| t.basename = P4RUBY_REMOTE_BASENAME } @s.specs = [ @s.p4ruby, @s.p4api ] @s.specs.each { |spec| spec.local = DISTFILES_DIR + spec.basename } #The below determines the latest version and correct platform, unless specified otherwise. unless @s.version @s.version = latest_version end @s.version_dir = SERVER_TOP_DIR + "r#{@s.version}" unless @s.platform @s.platform = guess_platform #There will me a method to work this out and the result will be stored in this var end if @s.platform =~ /nt/ @s.p4api.remote = @s.version_dir + "bin.#{@s.platform}" else @s.p4api.remote = @s.version_dir + "bin.#{@s.platform}" + @s.p4api.basename @s.p4ruby.remote = @s.version_dir + "bin.tools" + @s.p4ruby.basename end end #Guesses the CPU the machine is using. #May be able to get this info after download from the p4conf.rb file, unless we use it before to help determine which version of P4API to get. def guess_cpu if RbConfig["target_os"] =~ %r!darwin! if RbConfig["build"] =~ /i686|x86_64/ "x86_64" else "x86" end else case CONFIG["target_cpu"] when %r!ia!i "ia64" when %r!86! # note: with '_' "x86" + (BIT64 ? "_64" : "") when %r!(ppc|sparc)!i # note: without '_' $1 + (BIT64 ? "64" : "") else "" end end end def guess_version(os) if match = `uname -a`.match(%r!#{os}\s+\S+\s+(\d+)\.(\d+)!i) version = match.captures.join cpu = guess_cpu platforms = self.platforms built_platforms = (0..version.to_i).map { |n| [os, n.to_s, cpu].join }.select { |platform| platforms.include? platform } if os =~ /darwin/ built_platforms.pop built_platforms.last else built_platforms.last end else nil end end def guess_platform(opts = {}) config_os = RbConfig["target_os"].downcase windows_cpu = BIT64 ? "x64" : "x86" if config_os =~ %r!cygwin!i "cygwin" + windows_cpu elsif config_os =~ %r!(mswin|mingw)!i "nt" + windows_cpu elsif @s.local "<local>" else if match = config_os.match(%r!\A\D+!) guess_version(match[0]) else nil end end end def platform_fail install_fail { @s.version = "<version>" @s.platform = "<platform>" message = %Q{ Auto-fetch not yet handled for this platform. Run: \truby install.rb --list-platforms to see the available platforms, then run \truby install.rb --platform PLATFORM with your platform. If all of the above fails, manually fetch \tftp://#{SERVER}/#{@s.p4api.remote} Copy it to #{@s.p4api.local} and run install.rb --local. }.gsub(%r!^ +(?=\S)!, "") mkdir_p(DISTFILES_DIR) puts message } end def install_fail yield exit(1) end def sys(*args) system(*args).tap { |result| unless result raise "system() failed: #{args.join(" ")}" end } end #To manage the extraction and placing of the P4Ruby components. def unpack(distfile, target_dir) sys("tar", "zxvf", distfile.to_s, "-C", target_dir.to_s) end def fetch_spec(spec) unless @s.local mkdir_p(spec.local.dirname) puts "downloading ftp://#{SERVER}/#{spec.remote} ..." @s.ftp.getbinaryfile(spec.remote.to_s, spec.local.to_s) end end def fetch @s.specs.each { |spec| fetch_spec(spec) } end def remote_files_matching(dir, regex) @s.ftp.ls(dir.to_s).map { |entry| if match = entry.match(regex) yield match else nil end }.reject { |entry| entry.nil? } end def platforms remote_files_matching(@s.version_dir, %r!bin\.(\w+)!) { |match| match.captures.first }.reject { |platform| platform =~ %r!java! }.sort end def versions remote_files_matching(SERVER_TOP_DIR, %r!r([0-8]\d\.\d)!) { |match| match.captures.first }.reject { |version| HOSED_VERSIONS.include? version }.sort end def latest_version versions.reverse_each{ |v| begin remote_files_matching("#{SERVER_TOP_DIR}/r#{v}/bin.tools",/p4ruby/) do return v end rescue next end } end def make(*args) sys("make", *args) end def ruby(*args) exe = Pathname.new(CONFIG["bindir"]) + RbConfig["RUBY_INSTALL_NAME"] sys(exe.to_s, *args) end def build puts "building..." rm_rf(BUILD_DIR) mkdir_p(BUILD_DIR) @s.specs.each { |spec| unpack(spec.local, BUILD_DIR) } Dir.chdir(BUILD_DIR) { api_dir = Pathname.glob("p4api*").last p4ruby_dir = Pathname.glob("p4ruby*").last Dir.chdir(p4ruby_dir) { ruby("p4conf.rb", "--apidir", "../#{api_dir}") make } @s.p4ruby_build_dir = BUILD_DIR + p4ruby_dir } end def raw_install_to_gem_install RAW_INSTALL_FILES.zip(GEM_INSTALL_FILES) { |source, dest| mkdir_p(dest.dirname) puts "move #{source} --> #{dest}" mv(source, dest) } end def install puts "installing..." Dir.chdir(@s.p4ruby_build_dir) { make("install") } if @s.gem_config raw_install_to_gem_install end end def verify_install(on_error = nil) puts "verifying..." files = if @s.gem_config GEM_INSTALL_FILES else RAW_INSTALL_FILES end.map { |t| t.expand_path } if files.all? { |t| t.exist? } puts "Installed files:" puts files elsif on_error install_fail(&on_error) else install_fail { puts "These files were supposed to be installed, but were not:" puts files puts "Install failed!" } end end def find_ruby_version(spec) remote_files_matching(spec.remote, /p4ruby\d\d.exe/) {|r_ver| #Find the latest version of p4ruby for this version of ruby v_max = CONFIG["MAJOR"] v_min = CONFIG["MINOR"] version = [v_max, v_min].join if r_ver.to_s =~ /p4ruby#{version}.exe/ return "p4ruby#{version}.exe" end } nil end #If we bundled binaries with the gem we wouldn't need this def windows_install # # For Windows, p4ruby is located in the p4api directory on the # perforce server -- switcharoo -- # spec = @s.p4api p4ruby_exe = find_ruby_version(spec) if p4ruby_exe && !(spec.remote.to_s =~ /p4ruby/) spec.remote += p4ruby_exe.to_s else abort("Failed to find a suitable p4ruby executable for ruby #{CONFIG["MAJOR"]}.#{CONFIG["MINOR"]}") end fetch_spec(spec) error = lambda { puts "The Perforce P4Ruby Windows installer failed!" puts "You may re-run it manually here:" puts spec.local.expand_path } puts "running Perforce P4Ruby Windows installer..." if system(spec.local.to_s, "/S", "/v", "/qn") if @s.gem_config sleep(1) raw_install_to_gem_install sleep(1) #Without the -x flag a permissions error raised on Windows. unless system(spec.local, "/V", "/S", "/x", "/v/qn") # We don't much care if this fails; just write to the log puts "Note: the Perforce P4Ruby Windows uninstaller failed." end end verify_install(error) else install_fail(&error) end end def uninstall RAW_INSTALL_FILES.each { |file| if file.exist? puts "delete #{file}" rm_f(file) end } end #Hoping we don't need all the lazy struct stuff. # # An OpenStruct with optional lazy-evaluated fields. # class LazyStruct < OpenStruct # # For mixing into an existing OpenStruct instance singleton class. # module Mixin # # &block is evaluated when this attribute is requested. The # same result is returned for subsquent calls, until the field # is assigned a different value. # def attribute(reader, &block) singleton = (class << self ; self ; end) singleton.instance_eval { # # Define a special reader method in the singleton class. # define_method(reader) { block.call.tap { |value| # # The value has been computed. Replace this method with a # one-liner giving the value. # singleton.instance_eval { remove_method(reader) define_method(reader) { value } } } } # # Revert to the old OpenStruct behavior when the writer is called. # writer = "#{reader}=".to_sym define_method(writer) { |value| singleton.instance_eval { remove_method(reader) remove_method(writer) } method_missing(writer, value) } } end end include Mixin end end #Now create the instance of the Installer class and run. Installer.new.run
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#10 | 10176 | Jen Bottom |
Changed the logic for finding out if the customer has entered the required values in the variable sslbuild. Seems to function ok, but need to test that what is build with system ssl libs connects to ssl enabled P4D before submitting to RubyGems. |
||
#9 | 10169 | Jen Bottom |
Added a check for whether the variable sslbuild is empty. If it is instruct the user to select y or n. Still need to add handling for cases where the user enters something other than y or n. |
||
#8 | 10168 | Jen Bottom |
Adding a mechanism for the user to specify whether they want P4Ruby built with SSL support. Need to add error checking, for if the variable is empty or a value other than 'y' or 'n' is entered. |
||
#7 | 9464 | Jen Bottom | Pulling in Brett's changes. | ||
#6 | 9459 | Jen Bottom | Integrating Brett's changes into my branch. | ||
#5 | 9257 | Jen Bottom | deleting p4config file and adding to .ignore file. | ||
#4 | 9256 | Jen Bottom |
Fixed a typo. 'test.b' should have been 'test.rb'. |
||
#3 | 8879 | Jen Bottom | integrating Brett Bates's latest work on P4Ruby gem for further enhancement. | ||
#2 | 8483 | Jen Bottom |
submitting some minor changes to the 'install.rb' file. Have replaced 'config' with 'rbconfig'. Have also removed the build of the gem, as now it will be out of date |
||
#1 | 8366 | Jen Bottom | Adding the source code and docs for the P4Ruby Gem to the Public Depot. |