1*4882a593Smuzhiyun""" 2*4882a593SmuzhiyunBitBake 'Fetch' implementation for perforce 3*4882a593Smuzhiyun 4*4882a593SmuzhiyunSupported SRC_URI options are: 5*4882a593Smuzhiyun 6*4882a593Smuzhiyun- module 7*4882a593Smuzhiyun The top-level location to fetch while preserving the remote paths 8*4882a593Smuzhiyun 9*4882a593Smuzhiyun The value of module can point to either a directory or a file. The result, 10*4882a593Smuzhiyun in both cases, is that the fetcher will preserve all file paths starting 11*4882a593Smuzhiyun from the module path. That is, the top-level directory in the module value 12*4882a593Smuzhiyun will also be the top-level directory in P4DIR. 13*4882a593Smuzhiyun 14*4882a593Smuzhiyun- remotepath 15*4882a593Smuzhiyun If the value "keep" is given, the full depot location of each file is 16*4882a593Smuzhiyun preserved in P4DIR. This option overrides the effect of the module option. 17*4882a593Smuzhiyun 18*4882a593Smuzhiyun""" 19*4882a593Smuzhiyun 20*4882a593Smuzhiyun# Copyright (C) 2003, 2004 Chris Larson 21*4882a593Smuzhiyun# Copyright (C) 2016 Kodak Alaris, Inc. 22*4882a593Smuzhiyun# 23*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only 24*4882a593Smuzhiyun# 25*4882a593Smuzhiyun# Based on functions from the base bb module, Copyright 2003 Holger Schurig 26*4882a593Smuzhiyun 27*4882a593Smuzhiyunimport os 28*4882a593Smuzhiyunimport bb 29*4882a593Smuzhiyunfrom bb.fetch2 import FetchMethod 30*4882a593Smuzhiyunfrom bb.fetch2 import FetchError 31*4882a593Smuzhiyunfrom bb.fetch2 import logger 32*4882a593Smuzhiyunfrom bb.fetch2 import runfetchcmd 33*4882a593Smuzhiyun 34*4882a593Smuzhiyunclass PerforceProgressHandler (bb.progress.BasicProgressHandler): 35*4882a593Smuzhiyun """ 36*4882a593Smuzhiyun Implements basic progress information for perforce, based on the number of 37*4882a593Smuzhiyun files to be downloaded. 38*4882a593Smuzhiyun 39*4882a593Smuzhiyun The p4 print command will print one line per file, therefore it can be used 40*4882a593Smuzhiyun to "count" the number of files already completed and give an indication of 41*4882a593Smuzhiyun the progress. 42*4882a593Smuzhiyun """ 43*4882a593Smuzhiyun def __init__(self, d, num_files): 44*4882a593Smuzhiyun self._num_files = num_files 45*4882a593Smuzhiyun self._count = 0 46*4882a593Smuzhiyun super(PerforceProgressHandler, self).__init__(d) 47*4882a593Smuzhiyun 48*4882a593Smuzhiyun # Send an initial progress event so the bar gets shown 49*4882a593Smuzhiyun self._fire_progress(-1) 50*4882a593Smuzhiyun 51*4882a593Smuzhiyun def write(self, string): 52*4882a593Smuzhiyun self._count = self._count + 1 53*4882a593Smuzhiyun 54*4882a593Smuzhiyun percent = int(100.0 * float(self._count) / float(self._num_files)) 55*4882a593Smuzhiyun 56*4882a593Smuzhiyun # In case something goes wrong, we try to preserve our sanity 57*4882a593Smuzhiyun if percent > 100: 58*4882a593Smuzhiyun percent = 100 59*4882a593Smuzhiyun 60*4882a593Smuzhiyun self.update(percent) 61*4882a593Smuzhiyun 62*4882a593Smuzhiyun super(PerforceProgressHandler, self).write(string) 63*4882a593Smuzhiyun 64*4882a593Smuzhiyunclass Perforce(FetchMethod): 65*4882a593Smuzhiyun """ Class to fetch from perforce repositories """ 66*4882a593Smuzhiyun def supports(self, ud, d): 67*4882a593Smuzhiyun """ Check to see if a given url can be fetched with perforce. """ 68*4882a593Smuzhiyun return ud.type in ['p4'] 69*4882a593Smuzhiyun 70*4882a593Smuzhiyun def urldata_init(self, ud, d): 71*4882a593Smuzhiyun """ 72*4882a593Smuzhiyun Initialize perforce specific variables within url data. If P4CONFIG is 73*4882a593Smuzhiyun provided by the env, use it. If P4PORT is specified by the recipe, use 74*4882a593Smuzhiyun its values, which may override the settings in P4CONFIG. 75*4882a593Smuzhiyun """ 76*4882a593Smuzhiyun ud.basecmd = d.getVar("FETCHCMD_p4") or "/usr/bin/env p4" 77*4882a593Smuzhiyun 78*4882a593Smuzhiyun ud.dldir = d.getVar("P4DIR") or (d.getVar("DL_DIR") + "/p4") 79*4882a593Smuzhiyun 80*4882a593Smuzhiyun path = ud.url.split('://')[1] 81*4882a593Smuzhiyun path = path.split(';')[0] 82*4882a593Smuzhiyun delim = path.find('@'); 83*4882a593Smuzhiyun if delim != -1: 84*4882a593Smuzhiyun (ud.user, ud.pswd) = path.split('@')[0].split(':') 85*4882a593Smuzhiyun ud.path = path.split('@')[1] 86*4882a593Smuzhiyun else: 87*4882a593Smuzhiyun ud.path = path 88*4882a593Smuzhiyun 89*4882a593Smuzhiyun ud.usingp4config = False 90*4882a593Smuzhiyun p4port = d.getVar('P4PORT') 91*4882a593Smuzhiyun 92*4882a593Smuzhiyun if p4port: 93*4882a593Smuzhiyun logger.debug('Using recipe provided P4PORT: %s' % p4port) 94*4882a593Smuzhiyun ud.host = p4port 95*4882a593Smuzhiyun else: 96*4882a593Smuzhiyun logger.debug('Trying to use P4CONFIG to automatically set P4PORT...') 97*4882a593Smuzhiyun ud.usingp4config = True 98*4882a593Smuzhiyun p4cmd = '%s info | grep "Server address"' % ud.basecmd 99*4882a593Smuzhiyun bb.fetch2.check_network_access(d, p4cmd, ud.url) 100*4882a593Smuzhiyun ud.host = runfetchcmd(p4cmd, d, True) 101*4882a593Smuzhiyun ud.host = ud.host.split(': ')[1].strip() 102*4882a593Smuzhiyun logger.debug('Determined P4PORT to be: %s' % ud.host) 103*4882a593Smuzhiyun if not ud.host: 104*4882a593Smuzhiyun raise FetchError('Could not determine P4PORT from P4CONFIG') 105*4882a593Smuzhiyun 106*4882a593Smuzhiyun # Fetcher options 107*4882a593Smuzhiyun ud.module = ud.parm.get('module') 108*4882a593Smuzhiyun ud.keepremotepath = (ud.parm.get('remotepath', '') == 'keep') 109*4882a593Smuzhiyun 110*4882a593Smuzhiyun if ud.path.find('/...') >= 0: 111*4882a593Smuzhiyun ud.pathisdir = True 112*4882a593Smuzhiyun else: 113*4882a593Smuzhiyun ud.pathisdir = False 114*4882a593Smuzhiyun 115*4882a593Smuzhiyun # Avoid using the "/..." syntax in SRC_URI when a module value is given 116*4882a593Smuzhiyun if ud.pathisdir and ud.module: 117*4882a593Smuzhiyun raise FetchError('SRC_URI depot path cannot not end in /... when a module value is given') 118*4882a593Smuzhiyun 119*4882a593Smuzhiyun cleanedpath = ud.path.replace('/...', '').replace('/', '.') 120*4882a593Smuzhiyun cleanedhost = ud.host.replace(':', '.') 121*4882a593Smuzhiyun 122*4882a593Smuzhiyun cleanedmodule = "" 123*4882a593Smuzhiyun # Merge the path and module into the final depot location 124*4882a593Smuzhiyun if ud.module: 125*4882a593Smuzhiyun if ud.module.find('/') == 0: 126*4882a593Smuzhiyun raise FetchError('module cannot begin with /') 127*4882a593Smuzhiyun ud.path = os.path.join(ud.path, ud.module) 128*4882a593Smuzhiyun 129*4882a593Smuzhiyun # Append the module path to the local pkg name 130*4882a593Smuzhiyun cleanedmodule = ud.module.replace('/...', '').replace('/', '.') 131*4882a593Smuzhiyun cleanedpath += '--%s' % cleanedmodule 132*4882a593Smuzhiyun 133*4882a593Smuzhiyun ud.pkgdir = os.path.join(ud.dldir, cleanedhost, cleanedpath) 134*4882a593Smuzhiyun 135*4882a593Smuzhiyun ud.setup_revisions(d) 136*4882a593Smuzhiyun 137*4882a593Smuzhiyun ud.localfile = d.expand('%s_%s_%s_%s.tar.gz' % (cleanedhost, cleanedpath, cleanedmodule, ud.revision)) 138*4882a593Smuzhiyun 139*4882a593Smuzhiyun def _buildp4command(self, ud, d, command, depot_filename=None): 140*4882a593Smuzhiyun """ 141*4882a593Smuzhiyun Build a p4 commandline. Valid commands are "changes", "print", and 142*4882a593Smuzhiyun "files". depot_filename is the full path to the file in the depot 143*4882a593Smuzhiyun including the trailing '#rev' value. 144*4882a593Smuzhiyun """ 145*4882a593Smuzhiyun p4opt = "" 146*4882a593Smuzhiyun 147*4882a593Smuzhiyun if ud.user: 148*4882a593Smuzhiyun p4opt += ' -u "%s"' % (ud.user) 149*4882a593Smuzhiyun 150*4882a593Smuzhiyun if ud.pswd: 151*4882a593Smuzhiyun p4opt += ' -P "%s"' % (ud.pswd) 152*4882a593Smuzhiyun 153*4882a593Smuzhiyun if ud.host and not ud.usingp4config: 154*4882a593Smuzhiyun p4opt += ' -p %s' % (ud.host) 155*4882a593Smuzhiyun 156*4882a593Smuzhiyun if hasattr(ud, 'revision') and ud.revision: 157*4882a593Smuzhiyun pathnrev = '%s@%s' % (ud.path, ud.revision) 158*4882a593Smuzhiyun else: 159*4882a593Smuzhiyun pathnrev = '%s' % (ud.path) 160*4882a593Smuzhiyun 161*4882a593Smuzhiyun if depot_filename: 162*4882a593Smuzhiyun if ud.keepremotepath: 163*4882a593Smuzhiyun # preserve everything, remove the leading // 164*4882a593Smuzhiyun filename = depot_filename.lstrip('/') 165*4882a593Smuzhiyun elif ud.module: 166*4882a593Smuzhiyun # remove everything up to the module path 167*4882a593Smuzhiyun modulepath = ud.module.rstrip('/...') 168*4882a593Smuzhiyun filename = depot_filename[depot_filename.rfind(modulepath):] 169*4882a593Smuzhiyun elif ud.pathisdir: 170*4882a593Smuzhiyun # Remove leading (visible) path to obtain the filepath 171*4882a593Smuzhiyun filename = depot_filename[len(ud.path)-1:] 172*4882a593Smuzhiyun else: 173*4882a593Smuzhiyun # Remove everything, except the filename 174*4882a593Smuzhiyun filename = depot_filename[depot_filename.rfind('/'):] 175*4882a593Smuzhiyun 176*4882a593Smuzhiyun filename = filename[:filename.find('#')] # Remove trailing '#rev' 177*4882a593Smuzhiyun 178*4882a593Smuzhiyun if command == 'changes': 179*4882a593Smuzhiyun p4cmd = '%s%s changes -m 1 //%s' % (ud.basecmd, p4opt, pathnrev) 180*4882a593Smuzhiyun elif command == 'print': 181*4882a593Smuzhiyun if depot_filename is not None: 182*4882a593Smuzhiyun p4cmd = '%s%s print -o "p4/%s" "%s"' % (ud.basecmd, p4opt, filename, depot_filename) 183*4882a593Smuzhiyun else: 184*4882a593Smuzhiyun raise FetchError('No depot file name provided to p4 %s' % command, ud.url) 185*4882a593Smuzhiyun elif command == 'files': 186*4882a593Smuzhiyun p4cmd = '%s%s files //%s' % (ud.basecmd, p4opt, pathnrev) 187*4882a593Smuzhiyun else: 188*4882a593Smuzhiyun raise FetchError('Invalid p4 command %s' % command, ud.url) 189*4882a593Smuzhiyun 190*4882a593Smuzhiyun return p4cmd 191*4882a593Smuzhiyun 192*4882a593Smuzhiyun def _p4listfiles(self, ud, d): 193*4882a593Smuzhiyun """ 194*4882a593Smuzhiyun Return a list of the file names which are present in the depot using the 195*4882a593Smuzhiyun 'p4 files' command, including trailing '#rev' file revision indicator 196*4882a593Smuzhiyun """ 197*4882a593Smuzhiyun p4cmd = self._buildp4command(ud, d, 'files') 198*4882a593Smuzhiyun bb.fetch2.check_network_access(d, p4cmd, ud.url) 199*4882a593Smuzhiyun p4fileslist = runfetchcmd(p4cmd, d, True) 200*4882a593Smuzhiyun p4fileslist = [f.rstrip() for f in p4fileslist.splitlines()] 201*4882a593Smuzhiyun 202*4882a593Smuzhiyun if not p4fileslist: 203*4882a593Smuzhiyun raise FetchError('Unable to fetch listing of p4 files from %s@%s' % (ud.host, ud.path)) 204*4882a593Smuzhiyun 205*4882a593Smuzhiyun count = 0 206*4882a593Smuzhiyun filelist = [] 207*4882a593Smuzhiyun 208*4882a593Smuzhiyun for filename in p4fileslist: 209*4882a593Smuzhiyun item = filename.split(' - ') 210*4882a593Smuzhiyun lastaction = item[1].split() 211*4882a593Smuzhiyun logger.debug('File: %s Last Action: %s' % (item[0], lastaction[0])) 212*4882a593Smuzhiyun if lastaction[0] == 'delete': 213*4882a593Smuzhiyun continue 214*4882a593Smuzhiyun filelist.append(item[0]) 215*4882a593Smuzhiyun 216*4882a593Smuzhiyun return filelist 217*4882a593Smuzhiyun 218*4882a593Smuzhiyun def download(self, ud, d): 219*4882a593Smuzhiyun """ Get the list of files, fetch each one """ 220*4882a593Smuzhiyun filelist = self._p4listfiles(ud, d) 221*4882a593Smuzhiyun if not filelist: 222*4882a593Smuzhiyun raise FetchError('No files found in depot %s@%s' % (ud.host, ud.path)) 223*4882a593Smuzhiyun 224*4882a593Smuzhiyun bb.utils.remove(ud.pkgdir, True) 225*4882a593Smuzhiyun bb.utils.mkdirhier(ud.pkgdir) 226*4882a593Smuzhiyun 227*4882a593Smuzhiyun progresshandler = PerforceProgressHandler(d, len(filelist)) 228*4882a593Smuzhiyun 229*4882a593Smuzhiyun for afile in filelist: 230*4882a593Smuzhiyun p4fetchcmd = self._buildp4command(ud, d, 'print', afile) 231*4882a593Smuzhiyun bb.fetch2.check_network_access(d, p4fetchcmd, ud.url) 232*4882a593Smuzhiyun runfetchcmd(p4fetchcmd, d, workdir=ud.pkgdir, log=progresshandler) 233*4882a593Smuzhiyun 234*4882a593Smuzhiyun runfetchcmd('tar -czf %s p4' % (ud.localpath), d, cleanup=[ud.localpath], workdir=ud.pkgdir) 235*4882a593Smuzhiyun 236*4882a593Smuzhiyun def clean(self, ud, d): 237*4882a593Smuzhiyun """ Cleanup p4 specific files and dirs""" 238*4882a593Smuzhiyun bb.utils.remove(ud.localpath) 239*4882a593Smuzhiyun bb.utils.remove(ud.pkgdir, True) 240*4882a593Smuzhiyun 241*4882a593Smuzhiyun def supports_srcrev(self): 242*4882a593Smuzhiyun return True 243*4882a593Smuzhiyun 244*4882a593Smuzhiyun def _revision_key(self, ud, d, name): 245*4882a593Smuzhiyun """ Return a unique key for the url """ 246*4882a593Smuzhiyun return 'p4:%s' % ud.pkgdir 247*4882a593Smuzhiyun 248*4882a593Smuzhiyun def _latest_revision(self, ud, d, name): 249*4882a593Smuzhiyun """ Return the latest upstream scm revision number """ 250*4882a593Smuzhiyun p4cmd = self._buildp4command(ud, d, "changes") 251*4882a593Smuzhiyun bb.fetch2.check_network_access(d, p4cmd, ud.url) 252*4882a593Smuzhiyun tip = runfetchcmd(p4cmd, d, True) 253*4882a593Smuzhiyun 254*4882a593Smuzhiyun if not tip: 255*4882a593Smuzhiyun raise FetchError('Could not determine the latest perforce changelist') 256*4882a593Smuzhiyun 257*4882a593Smuzhiyun tipcset = tip.split(' ')[1] 258*4882a593Smuzhiyun logger.debug('p4 tip found to be changelist %s' % tipcset) 259*4882a593Smuzhiyun return tipcset 260*4882a593Smuzhiyun 261*4882a593Smuzhiyun def sortable_revision(self, ud, d, name): 262*4882a593Smuzhiyun """ Return a sortable revision number """ 263*4882a593Smuzhiyun return False, self._build_revision(ud, d) 264*4882a593Smuzhiyun 265*4882a593Smuzhiyun def _build_revision(self, ud, d): 266*4882a593Smuzhiyun return ud.revision 267*4882a593Smuzhiyun 268