1# ex:ts=4:sw=4:sts=4:et
2# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
3"""
4BitBake 'Fetch' implementation for perforce
5
6"""
7
8# Copyright (C) 2003, 2004  Chris Larson
9# Copyright (C) 2016 Kodak Alaris, Inc.
10#
11# This program is free software; you can redistribute it and/or modify
12# it under the terms of the GNU General Public License version 2 as
13# published by the Free Software Foundation.
14#
15# This program is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License along
21# with this program; if not, write to the Free Software Foundation, Inc.,
22# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23#
24# Based on functions from the base bb module, Copyright 2003 Holger Schurig
25
26import os
27import logging
28import bb
29from   bb.fetch2 import FetchMethod
30from   bb.fetch2 import FetchError
31from   bb.fetch2 import logger
32from   bb.fetch2 import runfetchcmd
33
34class Perforce(FetchMethod):
35    """ Class to fetch from perforce repositories """
36    def supports(self, ud, d):
37        """ Check to see if a given url can be fetched with perforce. """
38        return ud.type in ['p4']
39
40    def urldata_init(self, ud, d):
41        """
42        Initialize perforce specific variables within url data.  If P4CONFIG is
43        provided by the env, use it.  If P4PORT is specified by the recipe, use
44        its values, which may override the settings in P4CONFIG.
45        """
46        ud.basecmd = d.getVar('FETCHCMD_p4')
47        if not ud.basecmd:
48            ud.basecmd = "/usr/bin/env p4"
49
50        ud.dldir = d.getVar('P4DIR')
51        if not ud.dldir:
52            ud.dldir = '%s/%s' % (d.getVar('DL_DIR'), 'p4')
53
54        path = ud.url.split('://')[1]
55        path = path.split(';')[0]
56        delim = path.find('@');
57        if delim != -1:
58            (ud.user, ud.pswd) = path.split('@')[0].split(':')
59            ud.path = path.split('@')[1]
60        else:
61            ud.path = path
62
63        ud.usingp4config = False
64        p4port = d.getVar('P4PORT')
65
66        if p4port:
67            logger.debug(1, 'Using recipe provided P4PORT: %s' % p4port)
68            ud.host = p4port
69        else:
70            logger.debug(1, 'Trying to use P4CONFIG to automatically set P4PORT...')
71            ud.usingp4config = True
72            p4cmd = '%s info | grep "Server address"' % ud.basecmd
73            bb.fetch2.check_network_access(d, p4cmd, ud.url)
74            ud.host = runfetchcmd(p4cmd, d, True)
75            ud.host = ud.host.split(': ')[1].strip()
76            logger.debug(1, 'Determined P4PORT to be: %s' % ud.host)
77            if not ud.host:
78                raise FetchError('Could not determine P4PORT from P4CONFIG')
79
80        if ud.path.find('/...') >= 0:
81            ud.pathisdir = True
82        else:
83            ud.pathisdir = False
84
85        cleanedpath = ud.path.replace('/...', '').replace('/', '.')
86        cleanedhost = ud.host.replace(':', '.')
87        ud.pkgdir = os.path.join(ud.dldir, cleanedhost, cleanedpath)
88
89        ud.setup_revisions(d)
90
91        ud.localfile = d.expand('%s_%s_%s.tar.gz' % (cleanedhost, cleanedpath, ud.revision))
92
93    def _buildp4command(self, ud, d, command, depot_filename=None):
94        """
95        Build a p4 commandline.  Valid commands are "changes", "print", and
96        "files".  depot_filename is the full path to the file in the depot
97        including the trailing '#rev' value.
98        """
99        p4opt = ""
100
101        if ud.user:
102            p4opt += ' -u "%s"' % (ud.user)
103
104        if ud.pswd:
105            p4opt += ' -P "%s"' % (ud.pswd)
106
107        if ud.host and not ud.usingp4config:
108            p4opt += ' -p %s' % (ud.host)
109
110        if hasattr(ud, 'revision') and ud.revision:
111            pathnrev = '%s@%s' % (ud.path, ud.revision)
112        else:
113            pathnrev = '%s' % (ud.path)
114
115        if depot_filename:
116            if ud.pathisdir: # Remove leading path to obtain filename
117                filename = depot_filename[len(ud.path)-1:]
118            else:
119                filename = depot_filename[depot_filename.rfind('/'):]
120            filename = filename[:filename.find('#')] # Remove trailing '#rev'
121
122        if command == 'changes':
123            p4cmd = '%s%s changes -m 1 //%s' % (ud.basecmd, p4opt, pathnrev)
124        elif command == 'print':
125            if depot_filename != None:
126                p4cmd = '%s%s print -o "p4/%s" "%s"' % (ud.basecmd, p4opt, filename, depot_filename)
127            else:
128                raise FetchError('No depot file name provided to p4 %s' % command, ud.url)
129        elif command == 'files':
130            p4cmd = '%s%s files //%s' % (ud.basecmd, p4opt, pathnrev)
131        else:
132            raise FetchError('Invalid p4 command %s' % command, ud.url)
133
134        return p4cmd
135
136    def _p4listfiles(self, ud, d):
137        """
138        Return a list of the file names which are present in the depot using the
139        'p4 files' command, including trailing '#rev' file revision indicator
140        """
141        p4cmd = self._buildp4command(ud, d, 'files')
142        bb.fetch2.check_network_access(d, p4cmd, ud.url)
143        p4fileslist = runfetchcmd(p4cmd, d, True)
144        p4fileslist = [f.rstrip() for f in p4fileslist.splitlines()]
145
146        if not p4fileslist:
147            raise FetchError('Unable to fetch listing of p4 files from %s@%s' % (ud.host, ud.path))
148
149        count = 0
150        filelist = []
151
152        for filename in p4fileslist:
153            item = filename.split(' - ')
154            lastaction = item[1].split()
155            logger.debug(1, 'File: %s Last Action: %s' % (item[0], lastaction[0]))
156            if lastaction[0] == 'delete':
157                continue
158            filelist.append(item[0])
159
160        return filelist
161
162    def download(self, ud, d):
163        """ Get the list of files, fetch each one """
164        filelist = self._p4listfiles(ud, d)
165        if not filelist:
166            raise FetchError('No files found in depot %s@%s' % (ud.host, ud.path))
167
168        bb.utils.remove(ud.pkgdir, True)
169        bb.utils.mkdirhier(ud.pkgdir)
170
171        for afile in filelist:
172            p4fetchcmd = self._buildp4command(ud, d, 'print', afile)
173            bb.fetch2.check_network_access(d, p4fetchcmd, ud.url)
174            runfetchcmd(p4fetchcmd, d, workdir=ud.pkgdir)
175
176        runfetchcmd('tar -czf %s p4' % (ud.localpath), d, cleanup=[ud.localpath], workdir=ud.pkgdir)
177
178    def clean(self, ud, d):
179        """ Cleanup p4 specific files and dirs"""
180        bb.utils.remove(ud.localpath)
181        bb.utils.remove(ud.pkgdir, True)
182
183    def supports_srcrev(self):
184        return True
185
186    def _revision_key(self, ud, d, name):
187        """ Return a unique key for the url """
188        return 'p4:%s' % ud.pkgdir
189
190    def _latest_revision(self, ud, d, name):
191        """ Return the latest upstream scm revision number """
192        p4cmd = self._buildp4command(ud, d, "changes")
193        bb.fetch2.check_network_access(d, p4cmd, ud.url)
194        tip = runfetchcmd(p4cmd, d, True)
195
196        if not tip:
197            raise FetchError('Could not determine the latest perforce changelist')
198
199        tipcset = tip.split(' ')[1]
200        logger.debug(1, 'p4 tip found to be changelist %s' % tipcset)
201        return tipcset
202
203    def sortable_revision(self, ud, d, name):
204        """ Return a sortable revision number """
205        return False, self._build_revision(ud, d)
206
207    def _build_revision(self, ud, d):
208        return ud.revision
209
210