/******************************************************************************* Copyright (c) 1997-2004, Perforce Software, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTR IBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL PERFORCE SOFTWARE, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ /******************************************************************************* * Name : p4clientapi.cc * * Author : Tony Smith <tony@perforce.com> or <tony@smee.org> * * Description : Ruby bindings for the Perforce API. Main interface to the * Perforce API. * ******************************************************************************/ #include <ruby.h> #include "undefdups.h" #include <clientapi.h> #include <i18napi.h> #include <strtable.h> #include <spec.h> #include "extconf.h" #include "debug.h" #include "p4result.h" #include "p4rubydebug.h" #include "clientuserruby.h" #include "specmgr.h" #include "p4clientapi.h" /******************************************************************************* * Our Ruby classes. ******************************************************************************/ extern VALUE cP4; // Base P4 class extern VALUE eP4; // Exception class #define M_TAGGED 0x01 #define M_PARSE_FORMS 0x02 #define IS_TAGGED(x) (x & M_TAGGED ) #define IS_PARSE_FORMS(x) (x & M_PARSE_FORMS ) P4ClientApi::P4ClientApi() { initCount = 0; debug = 0; server2 = 0; mode = 0; depth = 0; exceptionLevel = 2; maxResults = 0; maxScanRows = 0; prog = "unnamed p4ruby script"; } P4ClientApi::~P4ClientApi() { if ( initCount ) { Error e; client.Final( &e ); // Ignore errors } } int P4ClientApi::SetCharset( const char *c ) { if( P4RDB_COMMANDS ) fprintf( stderr, "[P4] Setting charset: %s\n", c ); CharSetApi::CharSet cs = CharSetApi::Lookup( c ); if( cs < 0 ) { if( exceptionLevel ) { StrBuf m; m = "Unknown or unsupported charset: "; m.Append( c ); Except( "P4#charset=", m.Text() ); } return 0; } charset = c; client.SetTrans( cs, cs, cs, cs ); return 1; } void P4ClientApi::SetProg( const char *p ) { prog = p; } void P4ClientApi::SetDebug( int d ) { debug = d; ui.SetDebug( d ); if( P4RDB_RPC ) p4debug.SetLevel( "rpc=5" ); else p4debug.SetLevel( "rpc=0" ); } // // connect to the Perforce server. // VALUE P4ClientApi::Connect() { if ( P4RDB_COMMANDS ) fprintf( stderr, "[P4] Connecting to Perforce\n" ); if ( initCount ) { rb_warn( "P4#connect - Perforce client already connected!" ); return Qtrue; } Error e; specDict.Clear(); // Don't cache specs across connections client.Init( &e ); if ( e.Test() && exceptionLevel ) Except( "P4#connect", &e ); if ( e.Test() ) return Qfalse; initCount++; return Qtrue; } // // Disconnect session // VALUE P4ClientApi::Disconnect() { if ( P4RDB_COMMANDS ) fprintf( stderr, "[P4] Disconnect\n" ); if ( ! initCount ) { rb_warn( "P4#disconnect - not connected" ); return Qtrue; } Error e; client.Final( &e ); initCount--; return Qtrue; } void P4ClientApi::Tagged( int enable ) { if( enable ) mode |= M_TAGGED; else mode &= ~M_TAGGED; } void P4ClientApi::ParseForms() { client.SetProtocol( "specstring", "" ); mode |= M_PARSE_FORMS + M_TAGGED ; } void P4ClientApi::SetApiLevel( int level ) { StrBuf b; b << level; client.SetProtocol( "api", b.Text() ); } // // Run returns the results of the command. If the client has not been // connected, then an exception is raised but errors from Perforce // commands are returned via the Errors() and ErrorCount() interfaces // and not via exceptions because one failure in a command applied to many // files would interrupt processing of all the other files if an exception // is raised. // VALUE P4ClientApi::Run( const char *cmd, int argc, char * const *argv ) { // Save the entire command string for our error messages. Makes it // easy to see where a script has gone wrong. StrBuf cmdString; cmdString << "\"p4 " << cmd; for( int i = 0; i < argc; i++ ) cmdString << " " << argv[ i ]; cmdString << "\""; if ( P4RDB_COMMANDS ) fprintf( stderr, "[P4] Executing %s\n", cmdString.Text() ); if ( depth ) { rb_warn( "Can't execute nested Perforce commands." ); return Qfalse; } if ( ! initCount && exceptionLevel ) Except( "P4#run", "not connected." ); if ( ! initCount ) return Qfalse; #if P4APIVER_ID >= 513026 // ClientApi::SetProg() was introduced in 2004.2 client.SetProg( prog.Text() ); #endif if( mode & M_TAGGED ) client.SetVar( "tag" ); // Clear out any results from the previous command ui.Reset(); depth++; RunCmd( cmd, &ui, argc, argv ); depth--; P4Result &results = ui.GetResults(); if( ui.LastSpecDef().Length() ) specDict.SetVar( cmd, ui.LastSpecDef() ); if ( results.ErrorCount() && exceptionLevel ) Except( "P4#run", "Errors during command execution", cmdString.Text() ); if ( results.WarningCount() && exceptionLevel > 1 ) Except( "P4#run", "Warnings during command execution",cmdString.Text()); return results.GetOutput(); } // // RunCmd is a private function to work around an obscure protocol // bug in 2000.[12] servers. Running a "p4 -Ztag client -o" messes up the // protocol so if they're running this command then we disconnect and // reconnect to refresh it. For efficiency, we only do this if the // server2 protocol is either 9 or 10 as other versions aren't affected. // void P4ClientApi::RunCmd( const char *cmd, ClientUser *ui, int argc, char * const *argv ) { // If maxresults or maxscanrows is set, enforce them now if( maxResults ) client.SetVar( "maxResults", maxResults ); if( maxScanRows ) client.SetVar( "maxScanRows", maxScanRows ); client.SetArgv( argc, argv ); client.Run( cmd, ui ); // Have to request server2 protocol *after* a command has been run. I // don't know why, but that's the way it is. if ( ! server2 ) { StrPtr *pv = client.GetProtocol( "server2" ); if ( pv ) server2 = pv->Atoi(); } if ( IS_TAGGED(mode) && StrRef( cmd ) == "client" && server2 >= 9 && server2 <= 10 ) { if ( argc && ( StrRef( argv[ 0 ] ) == "-o" ) ) { if ( P4RDB_COMMANDS ) printf( "Resetting client to avoid 2000.[12] protocol bug\n" ); Error e; client.Final( &e ); client.Init( &e ); // Pass any errors down to the UI, so they'll get picked up. if ( e.Test() ) ui->HandleError( &e ); } } } // // Parses a string supplied by the user into a hash. To do this we need // the specstring from the server. We try to cache those as we see them, // but the user may not have executed any commands to allow us to cache // them so we may have to fetch the spec first. // VALUE P4ClientApi::ParseSpec( const char * type, const char *form ) { if( ! IS_PARSE_FORMS(mode) ) { if( exceptionLevel ) Except( "P4#parse_spec", "Use parse_forms mode to parse specs." ); return Qfalse; } StrPtr *specDef = FetchSpecDef( type ); if ( !specDef ) { if( exceptionLevel ) { StrBuf m; m = "No spec definition for "; m.Append( type ); m.Append( " objects." ); Except( "P4#parse_spec", m.Text() ); } else { return Qfalse; } } // Got a specdef so now we can attempt to parse it. Error e; SpecDataTable specData; #if P4APIVER_ID >= 513538 Spec s( specDef->Text(), "", &e ); #else Spec s( specDef->Text(), "" ); #endif SpecMgr m; if( !e.Test() ) s.ParseNoValid( form, &specData, &e ); if ( e.Test() ) { if( exceptionLevel ) { Except( "P4#parse_spec", &e ); } else { return Qfalse; } } // Now we've parsed it, convert it into a P4::Spec object. return m.DictToSpec( specData.Dict(), specDef ); } // // Converts a hash supplied by the user into a string using the specstring // from the server. We may have to fetch the specstring first. // VALUE P4ClientApi::FormatSpec( const char * type, VALUE hash ) { if( ! IS_PARSE_FORMS(mode) ) { if( exceptionLevel ) Except( "P4#format_spec", "Use parse_forms mode to format specs." ); return Qfalse; } StrPtr *specDef = FetchSpecDef( type ); Error e; if ( !specDef ) { if( exceptionLevel ) { StrBuf m; m = "No spec definition for "; m.Append( type ); m.Append( " objects." ); Except( "P4#format_spec", m.Text() ); } else { return Qfalse; } } // Got a specdef so now we can attempt to convert. StrBuf buf; SpecMgr m; if( m.HashToText( hash, &buf, specDef, &e ) ) return rb_str_new2( buf.Text() ); if( exceptionLevel ) { StrBuf m; m = "Error converting hash to a string."; if( e.Test() ) e.Fmt( m, EF_PLAIN ); Except( "P4#format_spec", m.Text() ); } return Qnil; } // // Returns a hash whose keys contain the names of the fields in a spec of the // specified type. Not yet exposed to Ruby clients, but may be in future. // VALUE P4ClientApi::SpecFields( const char * type ) { if( ! IS_PARSE_FORMS(mode) ) { if( exceptionLevel ) Except( "P4#spec_fields", "Use parse_forms mode to work with specs." ); return Qfalse; } StrPtr *specDef = FetchSpecDef( type ); if ( !specDef ) { if( exceptionLevel ) { StrBuf m; m = "No spec definition for "; m.Append( type ); m.Append( " objects." ); Except( "P4#spec_fields", m.Text() ); } else { return Qfalse; } } SpecMgr m; return m.SpecFields( specDef ); } // // Raises an exception or returns Qfalse on bad input // VALUE P4ClientApi::SetInput( VALUE input ) { if ( P4RDB_COMMANDS ) fprintf( stderr, "[P4] Received input for next command\n" ); if ( ! ui.SetInput( input ) ) { if ( exceptionLevel ) Except( "P4#input", "Error parsing supplied data." ); else return Qfalse; } return Qtrue; } void P4ClientApi::GCMark() { if ( P4RDB_GC ) fprintf( stderr, "[P4] Ruby asked us to do garbage collection\n" ); // We don't hold Ruby objects. But our UI does. ui.GCMark(); } void P4ClientApi::Except( const char *func, const char *msg ) { StrBuf m; StrBuf errors; StrBuf warnings; int terminate = 0; m << "[" << func << "] " << msg; // Now append any errors and warnings to the text ui.GetResults().FmtErrors( errors ); ui.GetResults().FmtWarnings( warnings ); if( errors.Length() ) { m << "\n" << errors; terminate++; } if( exceptionLevel > 1 && warnings.Length() ) { m << "\n" << warnings; terminate++; } if( terminate ) m << "\n\n"; rb_raise( eP4, m.Text() ); } void P4ClientApi::Except( const char *func, const char *msg, const char *cmd ) { StrBuf m; m << msg; m << "( " << cmd << " )"; Except( func, m.Text() ); } void P4ClientApi::Except( const char *func, Error *e ) { StrBuf m; e->Fmt( &m ); Except( func, m.Text() ); } // // Fetch a spec definition from the cache - faulting it if it's not there. // StrPtr * P4ClientApi::FetchSpecDef( const char *type ) { StrPtr *sd = specDict.GetVar( type ); if( sd ) return sd; // Fault. Now we have to do something nasty. We're in parse_forms mode, so // we can run a "p4 XXXX -o" and discard the result - the specdef should // now be in the cache. Some spec types require us to name the spec in // question. Since we don't know, we use a bogus name. We include the // word p4ruby in it so people know where to look if they spot it in // their logs. char * bogusSpec = "__p4ruby_bogus_spec__"; char * argv[3] = { "-o", 0, 0 }; int argc = 1; // // For specs of the following types we need the bogus spec name // StrRef t( type ); if( t == "branch" || t == "label" || t == "depot" || t == "group" ) argv[ argc++ ] = bogusSpec; Run( type, argc, argv ); sd = specDict.GetVar( type ); if( sd ) return sd; // OK, now we're hosed. Raise an exception if the exception level permits, // otherwise return null if( exceptionLevel ) Except( "P4#FetchSpecDef", "Error getting spec definition from server!" ); return 0; }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#20 | 5953 | Tony Smith |
Bug fix: P4Ruby could crash attempting to parse a form when tagged mode is enabled, but form parsing was not. This only occurred when used against a 2005.2 or later server. |
||
#19 | 5792 | Tony Smith | Update P4Ruby to support 2006.2 beta API. | ||
#18 | 5791 | Tony Smith |
Add experimental support for passing a block to P4#run_resolve. The block is passed a P4::MergeData object encapsulating the context of each merge performed. The block should evaluate to a string indicating the desired result of the merge: 'ay', 'at', 'am', 's', etc. The P4::MergeData object contains information about the files involved in the merge and can invoke an external merge tool. This is still experimental at this stage so the interface may change as it evolves. |
||
#17 | 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 |
||
#16 | 5311 | Tony Smith |
Add new P4#api= method to allow users to lock scripts to a particular API level. This helps when upgrading to new servers that extend support for tagged output to hitherto unsupported commands (2005.2 did a lot of that). See the C/C++ API Release Notes for the full details, but by way of example, to lock scripts to the 2005.1 interface use: p4.api = 57 |
||
#15 | 5222 | Tony Smith |
Improve debug output in P4Ruby: p4.debug = 1 * Show commands being executed p4.debug = 2 * Show function calls p4.debug = 3 * Show data p4.debug = 4 * Show ruby garbage collection calls. Debug levels are cumulative as you'd expect. |
||
#14 | 5105 | Tony Smith |
Make P4Ruby include the text of any errors or warnings in exception messages. Previously the user had to call P4#errors() and/or P4#warnings() to display this information and if you didn't handle P4Exception specifically the message could look somewhat cryptic. This change hopefully makes it easier - particularly for newbies. Note that the text of warnings is not shown unless you're using exception level 2 (P4::RAISE_ALL). |
||
#13 | 4942 | Tony Smith |
Add support for Unicode servers to P4Ruby. This change adds two new interfaces, P4#charset= and P4#charset? to set and get the charset respectively. |
||
#12 | 4940 | Tony Smith |
Add (undoc'd) support for enabling/disabling tagged mode on a per-command basis. Also fixed a minor typo which was rendering an error message less useful than intended. |
||
#11 | 4870 | Tony Smith |
Call ClientApi::SetProg() every time a command is run rather than only once. This makes the log entries correctly record the script name against every command that the script runs. |
||
#10 | 4841 | Tony Smith |
Bug fix. Fix stupid uninitialized integer problem with MaxScanRows and MaxResults. |
||
#9 | 4809 | Tony Smith |
Add P4#maxresults= and P4#maxscanrows= methods to allow you to place explicit limits on the execution of individual commands. These limits remain in force for all subsequent commands until they are removed by setting them to zero. Port of new functionality from P4Perl. |
||
#8 | 4680 | Tony Smith |
Make P4Ruby return new P4::Spec objects instead of plain old hashes when parse_forms mode is in use. A P4::Spec object is derived from Hash so should be backwards compatible with previous code. P4::Spec provides limited fieldname validation on forms and accessor methods for quick and easy access to the fields in the form. The accessor methods are all prefixed with '_' to avoid colliding with methods from the Hash parent class. This is a little ugly, but deriving from hash is a big win, so it's worth it. This change also fixes a minor bug found along the way. Spec parsing and formatting wouldn't work with labels, branches, depots and groups unless you'd previously run a P4::fetch_label( <label> ), P4::fetch_branch( <branch> ) etc. etc. This is because the spec parsing code internally runs one of these commands in order to grab the specdef from the server but it wasn't providing a spec name. i.e. it was using 'p4 client -o' and assuming that this would work for other types of spec too. It does, but not for all spec types. So, now the spec parsing code will use a bogus name for the spec types that require it. |
||
#7 | 4666 | Tony Smith |
New ParseSpec() and FormatSpec() methods allow you to convert specs between hash and string representations easily. Shortcut methods Parse* and Format* are also defined. (i.e. FormatClient() and ParseLabel() etc.) New methods IsTagged() and IsParseForms() tell you if your client is in tagged/form parsing mode respectively. If you care. P4::Tag() is deprecated in favour of P4::Tagged(). P4::Tag() exists for backwards compatibility |
||
#6 | 4651 | Tony Smith |
Add format_spec() method and format_* shortcuts to make it easy to convert a spec in a hash back to its string form without sending it to the server. |
||
#5 | 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=. |
||
#4 | 4261 | Tony Smith |
Add support for parsing arbitrary specs from strings in Ruby space. Useful with spec depots. You might obtain the spec by running a "p4 print -q" against a file in a spec depot, but want to parse it into a Ruby hash. i.e. p4 = P4.new p4.parse_forms # Required! p4.connect buf = p4.run_print( "-q", "//specs/client/myclient" ) spec = p4.parse_client( buf ) # Or equivalently spec = p4.parse_spec( "client", buf ) |
||
#3 | 4157 | Tony Smith |
Copyright notice update. No functional change |
||
#2 | 1869 | Tony Smith |
Build in workaround for 2000.[12] protocol bug with "p4 client -o" in tagged mode so script writers don't have to think about it |
||
#1 | 1750 | Tony Smith | Build environment tweaks. | ||
//guest/tony_smith/perforce/API/Ruby/main/p4clientapi.cc | |||||
#10 | 1426 | Tony Smith |
Cleaned up the debug output a little. Introduced some debug levels so you can decide (roughly) what output you want to see. Level 1 shows command execution, connect and disconnect. Level 2 includes Level 1 and also shows the RPC callbacks as they happen. Level 3 includes 1 and 2 and also shows when Ruby garbage collection takes place. Converted all the simple methods of the form P4#meth( arg ) to aliases for P4#meth=. Added P4#debug= to complete the scheme. The P4#meth( arg ) forms are now deprecated. i.e. you should use: p4.user = "tony" and not: p4.user( "tony" ) It's just more Ruby-like. |
||
#9 | 1391 | Tony Smith |
Bug fix. Garbage collection can apparently run at any time (i.e. when you're in C space and not just when you're in Ruby space) and it was occasionally running in between adjacent "delete" and "new" statements when the result set was being reset. This change removes this race condition by making the result member of ClientUserRuby a permanently instantiated variable and extending the P4Result class so that it can reset itself in a way that GC respects. Now the only dynamically allocated C++ object is the top level P4ClientApi object. No functional change. |
||
#8 | 1165 | Tony Smith |
Minor reshuffle. Added the ability to disable exceptions completely if you don't like them or to have them raised only for errors (and not for warnings). Removed P4#warnings interface and replaced it with P4#exception_level. Some minor doc tweaks to go with the above change |
||
#7 | 1164 | Tony Smith |
Reworked exception handling (hopefully for the last time) in P4/Ruby. Now exceptions are raised on completion of Perforce commands if any errors or warnings were received as part of executing the command. This change also adds documentation, and indexes the Ruby interface off my main page. Bad form to combine so many changes in one changelist, but it's getting late and I want to get them submitted! |
||
#6 | 1083 | Tony Smith |
Sweeping change to exception handling and garbage collection. Exceptions are no longer raised for errors encoutered during execution of Perforce commands as that was causing processing to abort at the first error when several success messages may have been close behind. Now exceptions are raised for events which are fatal to the execution of commands - such as failure to connect to the Perforce server for example. For other errors, the user must call "p4.errors? " to determine whether or not errors occured and "p4.errors" to get an array of error messages. You can of course then raise exceptions yourself if you want to: begin client = p4.fetch_client if p4.errors? raise P4Exception, "p4 client -o failed" end rescue P4Exception => m puts( m ) p4.errors.each { |e| puts( e ) } end version.h got renamed because it conflicts with ruby's own version.h file. We may need to look in there at some point for ruby's version so I'm getting it out of the way now. Added gc_hack.h to make sure that GC works properly on all platforms now so Ruby shouldn't nuke any objects we're holding now. |
||
#5 | 1081 | Tony Smith |
Debugging and identification support. Adds two new methods: P4#identify() P4#debug( int ) |
||
#4 | 1029 | Tony Smith |
Fix. Was crashing if you attempted to run a command before connecting. Now correctly raises an exception. |
||
#3 | 1028 | Tony Smith |
Earlier garbage collection fix doesn't work on NT. Reverting it for now until I figure out how to do it properly. |
||
#2 | 1026 | Tony Smith |
Garbage collection update. Removed stale debug statement and added what I think is correct support for garbage collection. Now any Ruby vars supplied as input to a "p4 xxx -i" command will not be swept while we're using them. gc_mark() - Ruby's API function for the mark and sweep garbage collector has a duff prototype in Ruby 1.6.4 and 1.6.5 so I've had to add that here to keep GCC statisfied. Hopefully it can be removed later on. |
||
#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. |