xref: /OK3568_Linux_fs/yocto/poky/bitbake/lib/bb/fetch2/perforce.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
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