Perforce API for the .Net CLR P4.Net

Comparisons with other Perforce APIs

Other APIs

There are a number of Perforce APIs out there... P4Ruby, P4Python, P4Perl, and P4COM are some of the most popular. My goal was to make P4.Net as similar as possible to these. In general, if you're familiar with the Ruby/Python/Perl APIs, you'll be able to transition to P4.Net fairly quickly. The biggest conceptual difference is that those APIs are used in dynamic languages, and P4.Net targets static languages. As a result, P4.Net uses custom types where the other APIs use built-in types, and P4.Net will use separate methods/properties to distinguish actions that output different types.

Python Example

Let's just dive into a sample, and do a side-by-side comparison. In this case, I'll compare P4.Net using IronPython to P4Python using standard Python. Note, I don't mean to suggest that P4.Net/IronPython is superior to P4Python/Standard Python, I just want to show apples-to-apples comparisons, without interference from individual language features.

  # Standard Python and P4Python   # IronPython and P4.Net
       
      import clr
1     from System import Array, String
      clr.AddReferenceToFile('P4API.dll')
       
  import p4 as P4API   import P4API
       
  p4 = P4API.P4()   p4 = P4API.P4Connection()
2 p4.parse_forms()    
  p4.connect()   p4.Connect()
       
  lname = 'P4NetTestingSample'   lname = 'P4NetTestingSample'
       
  # build my label   # build my label
  labelForm = p4.fetch_label(lname)   labelForm = p4.Fetch_Form('label', lname)
  labelForm['Description'] = 'Created for P4.Net sample'   labelForm['Description'] = 'Created for P4.Net sample'
3 view = ['//guest/shawn_hladky/...']   view = Array[String](['//guest/shawn_hladky/...'])
  labelForm['View'] = view   labelForm.ArrayFields['View'] = view
  res = p4.save_label(labelForm)   res = p4.Save_Form(labelForm)
       
4 if len(p4.errors) > 0:   if res.HasErrors():
    for e in p4.errors: print e       for e in res.Errors: print e
       
  # My list of changes. This is totally arbitrary   # My list of changes. This is totally arbitrary
  changes=['5774', '5680', '5636', '5444']   changes=['5774', '5680', '5636', '5444']
       
  sorted_changes = changes.sort()   sorted_changes = changes.sort()
       
  # dictionary: keyed by file, value = revision   # dictionary: keyed by file, value = revision
  filerevs = {}   filerevs = {}
       
  # spin the description on each file   # spin the description on each file
5 for chg in p4.run_describe('-s', *changes):   for chg in p4.Run('describe', '-s', *changes):
6     depotFiles = chg['depotFile']       depotFiles = chg.ArrayFields['depotFile']
    revisions = chg['rev']       revisions = chg.ArrayFields['rev']
  for i in range(0, len(depotFiles)):   for i in range(0, len(depotFiles)):
      key,value = depotFiles[i], revisions[i]       key,value = depotFiles[i], revisions[i]
      filerevs[key]=value       filerevs[key]=value
       
  # convert the dictionary to a list   # convert the dictionary to a list
  flist = []   flist = []
  for k,v in filerevs.items():   for k,v in filerevs.items():
      flist.append("%s#%s" % (k,v))       flist.append("%s#%s" % (k,v))
       
  # now I want to run in non-parsed mode    
  p4.disconnect()    
  p4 = P4API.P4()    
7 p4.connect()    
       
  out = p4.run('labelsync', '-l', lname, *flist)   out = p4.RunUnParsed('labelsync', '-l', lname, *flist)
       
  for s in out: print s   for s in out: print s
       
  # delete the label to keep the public depot clean :-)   # delete the label to keep the public depot clean :-)
  p4.run('label', '-d', lname)   p4.RunUnParsed('label', '-d', lname)
       
  p4.disconnect()   p4.Disconnect()

Block 1

This is some overhead required for IronPython. It will load the CLR, and P4.Net.

Block 2

This chunk of code is establishing a connection to the Perforce server. There are a couple things unique to P4.Net worth noting here. Classes in P4.Net are more explicitly named. Here the connection class is named P4Connection. While we're talking about naming, P4.Net uses traditional .Net conventions, and all method names use camel case.

Also, note that P4.Net does not need the parse_forms() method. P4.Net is designed to be more statically typed than P4Python/P4Ruby/P4Perl. You use different methods to retrieve tagged vs. untagged output. Therefore, P4.Net auto-manages the connection, and will dynamically switch modes in the native C++ API when needed.

Block 3

There's several things going on in this chunk, so let's break it down line-by-line:

P4Python: labelForm = p4.fetch_label(lname)
P4.Net  : labelForm = p4.Fetch_Form('label', lname)

Since P4.Net isn't meant for dynamic languages, there are no shortcut methods available.

P4Python: labelForm['Description'] = 'Created for P4.Net sample'
P4.Net  : labelForm['Description'] = 'Created for P4.Net sample'

Identical syntax!

P4Python: view = ['//guest/shawn_hladky/...']
P4.Net  : view = Array[String](['//guest/shawn_hladky/...'])

IronPython doesn't explicitly convert Python lists to .Net Arrays.

P4Python: labelForm['View'] = view
P4.Net  : labelForm.ArrayFields['View'] = view

In P4.Net, you use the ArrayFields property when getting/setting fields with array values.

P4Python: res = p4.save_label(labelForm)
P4.Net  : res = p4.Save_Form(labelForm)

There's no shortcut methode available in P4.Net; however, sine the form is a rich object, it already knows the command, and doesn't need to be supplied with the Save_Form method.

Block 4

This is a subtle, but significant departure from the other APIs. In P4.Net, results are rich objects, not just simple lists. All warnings, errors, and messages are associated with the results object. In other APIs, these are part of the connection object, and are reset with each run.

Block 5 & 6

More of the same... no short-cut methods, and use the ArrayFields property. Only in this instance we're dealing with a P4Recordset instead of a form.

Block 7

In P4.Net, you use different methods to return tagged and untagged output. This means P4.Net auto-manages the connection for you, so no need to explicitly reset as other APIs require.