#******************************************************************************* # Usage: # # ruby p4conf.rb --apidir=<path to api> \ # --apibuild=<Perforce API build> \ # --gccver=[23] # # <Perforce API build> should be the identifier of the Perforce API # build you downloaded from the website. This is the same as the # directory you got it from, but without the leading "bin.". For # example, if you downloaded the API build from: # # http://www.perforce.com/downloads/perforce/r05.1/bin.linux26amd64/ # # then the API build identifier is 'linux26amd64' # # --gccver defaults to 3 if not specified. # #******************************************************************************* require 'mkmf' require 'rbconfig' require "getoptlong" class ApiVersion def initialize( major, minor = nil ) if( major.kind_of?( String ) && ! minor ) if( major =~ /(\d+)\.(\d+)/ ) major = $1 minor = $2 else raise( "Bad API version: #{major}" ) end end @major = major.to_i @minor = minor.to_i end attr_reader :major, :minor include Comparable def to_s "#{major}.#{minor}" end def to_i major << 8 | minor end def <=>( other ) hi = @major <=> other.major lo = @minor <=> other.minor return hi == 0 ? lo : hi end end #******************************************************************************* # Prompt the user for something #******************************************************************************* def prompt( str, testproc ) while true print( str + ": " ) val = $stdin.gets.chomp break if ( testproc.call( val ) ) end return val end #******************************************************************************* # Find out as much info about the platform as we can. Ideally we want to # determine the OS name, version and CPU architecture so we can decide # which compiler flags should be used for the Perforce API for that # platform. #******************************************************************************* def probe_platform # # Work out the target OS, Version, and Platform # $p4osplat = CONFIG[ 'host_cpu' ].upcase $p4osname = CONFIG[ 'host_os' ].upcase $p4osname = $p4osname.gsub( /MSWIN32(_\d+)?/, "NT" ) $p4osname = $p4osname.split( "-" ).shift $p4osver = "" # Translate Ruby's arch names into Perforce's. Mostly the same so # only the exceptions are handled here. if( $p4osplat =~ /^I.86$/ ) $p4osplat = "X86" elsif( $p4osplat == "X86_64" ) $p4osplat = "AMD64" elsif( $p4osplat == "POWERPC" ) $p4osplat = "PPC" end # Now work out the OS version if( $p4osname == "NT" ) $p4osver = "" elsif( $p4osname =~ /FREEBSD([0-9]+)/ ) $p4osname = "FREEBSD" $p4osver = $1 elsif( $p4osname =~ /DARWIN([0-9]+)/ ) $p4osname = "DARWIN" $p4osver = $1 elsif( $p4osname =~ /AIX(5)\.(\d)/ ) # AIX osname looks like "aix5.3.0.0" $p4osname = "AIX" $p4osver = $1 + $2 else # Try running a 'uname -r' to work out the version begin $p4osver=`uname -r` ver_re = /^(\d+)\.(\d+)/ if( md = ver_re.match( $p4osver ) ) maj = md[1].to_i min = md[2].to_i # Convert Solaris 5.6 into 2.6. if( $p4osname == "SOLARIS" ) maj -= 3 end $p4osver = maj.to_s + min.to_s end rescue # Nothing - if it failed, it failed. end end end #******************************************************************************* # Allow the user to override our guesses as to the platform and provide # an explicit API build version. #******************************************************************************* def override_platform( plat ) archs = %w{ x86 amd64 ppc sparc arm axp } # Build the regular expression we're going to use, first as a string plat_re = '(\D+)(\d+)?(' plat_re += archs.join( '|' ) plat_re += ')?' # Now convert to a regexp plat_re = Regexp.new( plat_re ) if( md = plat_re.match( plat ) ) $p4osname = md[ 1 ].upcase $p4osver = md[ 2 ] || "" $p4osplat = ( md[ 3 ] || "" ).upcase end end #******************************************************************************* # Add the #define for const_char='const char' on platforms that need it #******************************************************************************* def set_const_char() $CFLAGS += %Q{-Dconst_char="const char" } end #******************************************************************************* # Set platform specific compile options #******************************************************************************* def set_platform_cflags $CFLAGS += "-DOS_#{$p4osname} " $CFLAGS += "-DOS_#{$p4osname}#{$p4osver} " $CFLAGS += "-DOS_#{$p4osname}#{$p4osver}#{$p4osplat} " if( $p4osname == "NT" ) $CFLAGS += "/DCASE_INSENSITIVE " $CFLAGS += "/MT " end if( $p4osname == "SOLARIS" ) $CFLAGS += "-Dsolaris " # Solaris 2.7 and later require -Dconst_char="const char" if ( $p4osver.to_i >= 27 ) set_const_char() end elsif( $p4osname == "LINUX" && $p4osplat == "AMD64" ) set_const_char() end if( $p4osname == "DARWIN" ) $CFLAGS += "-DCASE_INSENSITIVE " end end #******************************************************************************* # Set platform specific build options #******************************************************************************* def set_platform_opts if ( $p4osname == "CYGWIN" && $p4gccver == 2 ) CONFIG[ "CC" ] = "gcc-2" CONFIG[ "CXX" ] = "g++-2" CONFIG[ "LDSHARED" ].gsub!( /g\+\+/, "g++-2" ) elsif ( $p4osname == "DARWIN" && $p4gccver == 2 ) CONFIG[ "CC" ] = "gcc2" CONFIG[ "CXX" ] = "g++2" CONFIG[ "LDSHARED" ].sub!( /^cc/, "g++2" ) elsif ( $p4osname == "DARWIN" ) CONFIG[ "LDSHARED" ] = CONFIG[ "CXX" ] + " -bundle" elsif ( $p4osname == "FREEBSD" ) # FreeBSD 6 at least uses cc for linking by default CONFIG[ "LDSHARED" ].sub!( /^cc/, "c++" ) end end #******************************************************************************* # Use platform libs. Call *before* adding the API libs to preserve linking # order. #******************************************************************************* def use_platform_libs if ( $p4osname == "SOLARIS" ) osver = `uname -r` osver.gsub!( /5\./, "2" ) if ( osver == "25" ) $LDFLAGS += "/usr/ucblib/libucb.a " end use_lib( "nsl" ) use_lib( "socket" ) elsif ( $p4osname == "NT" ) use_lib( "advapi32" ) use_lib( "wsock32" ) use_lib( "kernel32" ) use_lib( "oldnames" ) use_lib( "libcmt" ) elsif( $p4osname == "CYGWIN" ) # Clear out the bogus libs on cygwin. CONFIG[ "LIBS" ] = "" elsif( $p4osname == "DARWIN" && $p4osver.to_i >= 8 ) $LDFLAGS += " -framework Carbon" end end #******************************************************************************* # Set directory for the Perforce API #******************************************************************************* def set_apidir( path ) libdir = path + "/lib" libdir = File.directory?( libdir ) ? libdir : path hdrdir = path + "/include/p4" hdrdir = File.directory?( hdrdir ) ? hdrdir : path $LIBPATH = [ libdir ] | $LIBPATH $CFLAGS += " -I#{hdrdir} " end #******************************************************************************* # Get the version of the Perforce API in a directory #******************************************************************************* def get_api_version( dir ) # # 2007.2 and later APIs put the Version file in the 'sample' # subdirectory. Look there if we can't find it in the API root # ver_file = dir + "/Version" unless File.exists?( ver_file ) ver_file = dir + "/sample/Version" return nil unless File.exists?( ver_file ) end re = Regexp.new( '^RELEASE = (\d+)[\. ](\d+)' ) apiver = nil File.open( ver_file, "r" ) do |f| f.each_line do |line| if md = re.match( line ) apiver = ApiVersion.new( md[ 1 ], md[ 2 ] ) break end end end return apiver end #******************************************************************************* # Function to check that the version of the API used is digestible #******************************************************************************* def check_api_version( ver ) return true if ver <= $MAX_API_VERSION puts <<EOS This version of P4Ruby was designed to be used with the #{$MAX_API_VERSION} Perforce C++ API. It may not work with later versions of the API. EOS print( "Would you like to continue (y/n): " ) answer = $stdin.gets.chomp return false unless answer == "y" true end #******************************************************************************* # function for generating a correctly formatted #define string #******************************************************************************* def define( macro, value, string=true ) if( string ) %Q{#define #{macro}\t"#{value}"} else %Q{#define #{macro}\t#{value}} end end #******************************************************************************* #* Write the extconf.h file #******************************************************************************* def write_extconf_h File.open( "extconf.h", "w" ) do |ch| ch.puts( "#include <version.h>" ) ch.puts( define( "P4APIVER_STRING", $apiver ) ) ch.puts( define( "P4APIVER_ID", $apiver.to_i, false ) ) ch.puts( define( "P4OSNAME", $p4osname ) ) ch.puts( define( "P4OSPLAT", $p4osplat ) ) ch.puts( define( "P4OSVER", $p4osver ) ) ch.puts( define( "P4CFLAGS", $CFLAGS.gsub( '"', '\"' ) ) ) ch.puts( define( "P4LIBPATH", $LIBPATH.join( ":" ) ) ) ch.puts( define( "P4LIBS", $libs ) ) ch.puts <<EOS #if RUBY_VERSION_MAJOR == 1 && RUBY_VERSION_MINOR == 6 # define P4RUBY_FUNC_CAST( f ) ((VALUE (*)()) f) #else # define P4RUBY_FUNC_CAST( f ) ((VALUE (*)( VALUE )) f) #endif EOS end end #******************************************************************************* # Add a Perforce API library making sure it's properly named on NT #******************************************************************************* def api_lib( lib ) rlib = lib rlib = "lib" + lib if ( $p4osname == "NT" ) use_lib( rlib ) end #******************************************************************************* # Add a library to the link command bypassing lame have_library/find_library # which utterly suck for C++ libs. # NOTE: Add libs in *reverse* order. #******************************************************************************* def use_lib( lib ) $libs = append_library( $libs, lib ) end #******************************************************************************* # Function to write the makefile itself. This is mostly a wrapper around # mkmf's create_makefile(), but due to some deficiencies in mkmf w.r.t C++ # sources, we occasionally have to edit the makefile #******************************************************************************* def write_makefile( name ) create_makefile( name ) if ( CONFIG[ "CXX" ] ) # Then we need to add a definition of CXX to the makefile # or the build will fail on some platforms (Cygwin at least). File.unlink( "Makefile.tmp" ) if File.exists?( "Makefile.tmp" ) File.rename( "Makefile", "Makefile.tmp" ) inmf = File.open( "Makefile.tmp" ) outmf = File.open( "Makefile", "w+" ) seen = false inmf.each do |line| if ( !seen && line =~ /^CC = / ) outmf.puts( "CXX = " + CONFIG[ "CXX" ] ) seen = true end outmf.puts( line ) end inmf.close outmf.close File.unlink( "Makefile.tmp" ) end end #******************************************************************************* # Start of main functionality #******************************************************************************* # # Maximum version of the Perforce API this build of P4Ruby knows how to # work with # $MAX_API_VERSION = ApiVersion.new( 2007, 2 ) opts = GetoptLong.new( [ "--apidir", "-d", GetoptLong::REQUIRED_ARGUMENT ], [ "--apibuild", "-b", GetoptLong::REQUIRED_ARGUMENT ], [ "--gccver", GetoptLong::REQUIRED_ARGUMENT ] ) $apidir = nil $apiver = nil $apibuild = nil $p4gccver = 3 #******************************************************************************* # C++ programs should be linked with g++ not with gcc. mkmf doesn't get that # right, so we have to hack it a little here. This particularly affects # gcc 3.x users as older gcc versions didn't mind too much. Since mkmf is # pretty convoluted it seems the safest way to ensure that g++ is used is # to update the CC value as that ripples through the Makefile #******************************************************************************* if ( CONFIG[ "CC" ] =~ /gcc/ ) CONFIG[ "CC" ].sub!( /gcc/, "g++" ) CONFIG[ 'LDSHARED' ].sub!( /gcc/, "g++" ) CONFIG[ "CXX" ] = CONFIG[ "CC" ] unless CONFIG.has_key?( "CXX" ) end opts.each do |opt,arg| if ( opt == "--apidir" ) $apidir = File.expand_path( arg ) elsif( opt == "--apibuild" ) $apibuild = arg elsif( opt == "--gccver" ) $p4gccver = arg[0,1].to_i end end puts( "" ) puts( "Starting to configure P4/Ruby for building" ) puts( "" ) # # Guess the platform we're on, and then allow the user to override our # guesswork if necessary # probe_platform() override_platform( $apibuild ) # # If the user has not supplied the api directories or apiver we prompt for them # dirproc = Proc.new { |dir| dir = File.expand_path( dir ) if ( ! File.directory?( dir ) ) puts( "No such directory. Try again" ) false else true end } if ( ! $apidir ) $apidir = prompt( "Enter the path to the Perforce API", dirproc ) $apidir = File.expand_path( $apidir ) end # # Read the Version file in the API directory to find the API version string # and add this to the build environment # # # If we failed to work this out, we have to ask the user. # verproc = Proc.new { |ver| if( ver =~ /(\d+)\.(\d+)/ ) true else puts( "Not a valid Perforce version. Try again." ) false end } $apiver = get_api_version( $apidir ) if( !$apiver ) while( ! $apiver ) $apiver = prompt( "Enter the version of the Perforce API", verproc ) end $apiver = ApiVersion.new( $apiver ) end # # Check that the version of the API is one we can cope with, or that the # user wants to punt. # exit( 1 ) unless check_api_version( $apiver ) set_apidir( $apidir ) set_platform_opts() set_platform_cflags() # # 2006.1 or later API builds always need const_char defined # sixone = ApiVersion.new( 2006, 1 ) set_const_char() if( $apiver >= sixone ) use_platform_libs() # Perforce API libraries - in reverse order api_lib( "supp" ) api_lib( "rpc" ) api_lib( "client" ) # # OK, now we know what we're going to do, let's tell the user # puts <<EOS P4/Ruby Configuration Summary ----------------------------- Using Perforce API Version: #{$apiver} API headers and libs from : #{$apidir} OS name : #{$p4osname} OS version : #{$p4osver} Platform : #{$p4osplat} CFLAGS : #{$CFLAGS} LIBPATH : #{$LIBPATH.join(":")} libs : #{$libs} EOS puts( "Creating extconf.h" ) write_extconf_h write_makefile( "P4" )
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#31 | 6028 | Tony Smith | Improve p4conf.rb to support AIX 5.3 - thanks to Robert Cowham | ||
#30 | 6012 | Tony Smith |
Help P4Ruby build more cleanly on Windows with more recent Ruby's than my own. The host_os value on Windows can include the compiler version now i.e. mswin32_71 or mswin32_80, and that was causing p4conf.rb to get the -DOS_NT macro wrong (OS_NT_71), which caused builds to fail. No functional change. Thanks to Patrick Bennett. |
||
#29 | 5944 | Tony Smith |
Changes for building P4Ruby with 2007.2 API. This change also makes p4conf.rb check that the user is using an API version we can digest. In addition, I've widened some of the gcc/g++ pattern matches to catch some build problems recently observed on some Red Hat platforms. |
||
#28 | 5896 | Tony Smith |
Porting to OSX 10.4. Nothing we can do to fix 10.3, it was broken, but 10.4 has the fix P4Ruby needed, so this change makes it build cleanly on OSX 10.4. |
||
#27 | 5892 | Tony Smith |
Ensure that the Perforce API directory comes ahead of any other directories in the linker path. No functional change |
||
#26 | 5806 | Tony Smith |
FreeBSD 6 porting changes. No functional change |
||
#25 | 5795 | Tony Smith | Fix a typo in the previous change (it's just not my day) | ||
#24 | 5793 | Tony Smith |
Back-port previous change to Ruby 1.6. Unfortunately the prototype for rb_protect() changed between 1.6 and 1.8, so the calling code needs some special handling. A subsequent change will update the distribution and version as I'm submitting this from Windows and Cygwin's tar isn't up to the task of building the source tarball properly. |
||
#23 | 5768 | Tony Smith |
Build system tweaks: sort the quoting out for Windows builds. No functional change |
||
#22 | 5693 | Tony Smith |
Update p4conf.rb to define const_char on all platforms if building against a 2006.1 or later API. Also squelched some compiler noise. No functional change |
||
#21 | 5115 | Tony Smith |
Yet another change to the build system. My guesswork on how it would work on AMD64 was wrong, and since I now have access to an Opteron box with Ruby on it, I've ported and tested it directly. Architecture detection works nicely now, and the const_char macro is correctly defined. Porting change only - no functional change |
||
#20 | 5114 | Tony Smith |
Minor update to previous two changes. I originally opted to use CONFIG[ 'target_os' ] as it contained just what I was looking for, but it's not there in Ruby 1.6 so that was out. What is there in both 1.6 and 1.8 is 'host_os', but on Linux (and possibly others) it can contain a tuple value ('linux-gnu' instead of just 'linux'). This change splits the host_os field and takes only the first part so p4conf.rb should guess correctly in more cases (I think!). |
||
#19 | 5112 | Tony Smith | Port previous change to NT (and hopefully others). | ||
#18 | 5111 | Tony Smith |
Make P4Ruby's build script support the const_char macro definition on those platforms that need it (Solaris >= 2.7 and Linux on AMD64 primarily). If we ever port P4Ruby to LinuxIA64 we'll need to add support for that too. With this change p4conf.rb tries harder to determine the O/S name and version, but it's unlikely to get it right all the time. So there's also a new '--apibuild' flag to p4conf.rb that allows the user to override the detected platform with an explicit configuration. Most people won't have to use it, but it'll be there for those that need it. I've also updated the README with the new build procedure, and added a FAQ document for the most common questions. |
||
#17 | 4840 | Tony Smith |
Bug fix to P4Ruby's build script. On some Linux platforms, P4Ruby would still be linked with gcc rather than g++ causing problems for those using gcc 3.x. This change forces mkmf to generate a makefile where CC=g++ which does no harm to old environments but makes the link work on new environments. |
||
#16 | 4589 | Tony Smith |
Update P4Ruby to support the new SetProg() method in the 2004.2 API. Whilst the new 'P4#prog=' method is always available, it's only functional if P4Ruby is built with a 2004.2 or later API. The build system got a bit of tidying up to support this change and the API version is now detected automatically if possible. I've also removed the --apilibdir and --apiincludedir flags as they complicate matters and I never use them and I don't believe anyone else does either. There are also some minor doc formatting tweaks to go along with the added documentation for prog=. |
||
#15 | 4251 | Tony Smith |
Add support for delete_* methods to P4Ruby so you can now say: p4.delete_client( "clientname" ) instead of the old: p4.run_client( "-d", "clientname" ) Just some shorthand. Also some small changes to the setup script to allow for people who install the Perforce API in a system-wide location such as /usr/include/p4/ /usr/lib/p4/ Now the installer takes --apilibdir= and --apiincludedir= params as well as the old --apidir= |
||
#14 | 4190 | Tony Smith |
Port P4Ruby to MacOSX. This is a "sausages" build - you might like the results but you won't like the way it's done. In this case, it's so ugly that I'm ashamed to submit this change. It works, but MacOSX will remain an unsupported platform for P4Ruby as long as this hack is necessary. The basic problem is that the Signaler class in the Perforce API calls signal() on initialisation to install itself as the default handler for SIGINT signals. In a standalone executable that's fine on OSX, but ruby uses Apple's dyld interface to load the module into the process address space dynamically. When signal() is called at module load time, Apple's dyld implementation goes into an infaloop. This horrible piece of code provides a replacement (broken) implementation of the Signaler class that does not initialise itself in this way thereby avoiding the problem. The fact that this is broken should not cause any problems for P4Ruby users, but would not be advisable to use it elsewhere. I refuse to integrate this into the build process for P4Ruby, so people who want this port will have to follow the instructions in README.DARWIN and do it themselves. I have made a few minor porting changes to help with compiler selection on DARWIN though. |
||
#13 | 4136 | Tony Smith |
Porting change. Solaris 2.7 and above require an additional pre-processor definition to compile correctly |
||
#12 | 3578 | Tony Smith | Port P4Ruby to Ruby 1.8.0. | ||
#11 | 3541 | Tony Smith |
Porting change. Now builds under Cygwin with only a few filthy hacks. New undoc'd --gccver=2 flag allows Cygwin users to build P4Ruby using the gcc-2 package rather than the default compiler which is currently gcc 3.2.3. They have to do this because the Perforce API is not yet available in a gcc-3 version for Cygwin. |
||
#10 | 2924 | Tony Smith |
Porting changes. P4Ruby now builds with gcc 3.2 |
||
#9 | 2408 | Tony Smith |
Build process tweak. Rename config.h to extconf.h so that it will be removed by "make distclean" as that's what mkmf expects it to be called. |
||
#8 | 2395 | Tony Smith |
Port previous change to NT. Needed a few tweaks to get the libraries named correctly |
||
#7 | 2388 | Tony Smith |
Rework p4conf.rb script to improve the build process. There are now two mandatory parms and if you omit them you will be prompted for their values. --apidir <dir> - Path to the Perforce API --apiver <string> - API version string (e.g. 2002.2) I've also renamed the .so file from "P4api.so" to just P4.so and I've improved the self identification code to include the build flags and the API version to help with diagnosis. |
||
#6 | 1750 | Tony Smith | Build environment tweaks. | ||
#5 | 1177 | Tony Smith |
FreeBSD (3.3) port. Requires explicit link to libgcc |
||
#4 | 1167 | Tony Smith |
First go at cleaning up the NT build for P4/Ruby. Now generates a makefile that works with nmake. |
||
#3 | 1027 | Tony Smith |
Rework structure slightly. What was P4.so is now called P4api.so and a new P4.rb module is added. P4api.so contains the raw bridge to the Perforce API whilst P4.rb contains extensions to it written purely in Ruby. Both define and extend the P4 class and P4.rb requires P4api.so so user code is unaffected. The intent is to give me a place to write Ruby code rather than having to work solely in C++. The first method added using this new structure is P4#method_missing, so now Perforce commands all appear as methods on the P4 object (prefixed by "run_" ). e.g p4 = P4.new p4.run_info() p4.run_users() p4.run_protect( "-o" ) This change also adds support for shortcut methods for form editing. fetch* and save* methods have been added as shortcuts for "p4 <cmd> -o" and "p4 <cmd> -i" respectively. For example: p4 = P4.new p4.parse_forms() client_spec = p4.fetch_client() client_spec[ "Owner" ] = tony p4.save_client( client_spec ) Note that unlike the run* methods, these accessor methods do not return an array, but return a single result - a string normally, or a hash in parse_forms mode. Ruby's arcane build/install system means that you have to install the P4.rb file yourself. "make install" doesn't take care of it. |
||
#2 | 1019 | Tony Smith |
Some porting work to get it to compile on NT. mkmf stuff is still rubbish on NT (<smug>as I told ruby-talk it would be</smug>) and the generated makefile doesn't work at all. Had to figure it out and build by hand - yuk. |
||
#1 | 1015 | Tony Smith |
First cut of Perforce bindings for the Ruby scripting language. Similar functionality to the Perl API stuff, but "rubyfied". Supports error reporting via exceptions, and presents tagged output and parsed forms as hash structures, with nested arrays where required. Still early days so the docs are thin on the ground. See the example.pl for a brief guide. Built with Ruby 1.6.4 on Linux. May still be some memory management issues as the Ruby Garbage Collection API has changed a little since the docs I've got and I've just dodged garbage collection for now. Not indexing this just yet. |