1"""
2BitBake 'Fetch' clearcase implementation
3
4The clearcase fetcher is used to retrieve files from a ClearCase repository.
5
6Usage in the recipe:
7
8    SRC_URI = "ccrc://cc.example.org/ccrc;vob=/example_vob;module=/example_module"
9    SRCREV = "EXAMPLE_CLEARCASE_TAG"
10    PV = "${@d.getVar("SRCREV", False).replace("/", "+")}"
11
12The fetcher uses the rcleartool or cleartool remote client, depending on which one is available.
13
14Supported SRC_URI options are:
15
16- vob
17    (required) The name of the clearcase VOB (with prepending "/")
18
19- module
20    The module in the selected VOB (with prepending "/")
21
22    The module and vob parameters are combined to create
23    the following load rule in the view config spec:
24                load <vob><module>
25
26- proto
27    http or https
28
29Related variables:
30
31    CCASE_CUSTOM_CONFIG_SPEC
32            Write a config spec to this variable in your recipe to use it instead
33            of the default config spec generated by this fetcher.
34            Please note that the SRCREV loses its functionality if you specify
35            this variable. SRCREV is still used to label the archive after a fetch,
36            but it doesn't define what's fetched.
37
38User credentials:
39    cleartool:
40            The login of cleartool is handled by the system. No special steps needed.
41
42    rcleartool:
43            In order to use rcleartool with authenticated users an `rcleartool login` is
44            necessary before using the fetcher.
45"""
46# Copyright (C) 2014 Siemens AG
47#
48# SPDX-License-Identifier: GPL-2.0-only
49#
50
51import os
52import shutil
53import bb
54from   bb.fetch2 import FetchMethod
55from   bb.fetch2 import FetchError
56from   bb.fetch2 import MissingParameterError
57from   bb.fetch2 import ParameterError
58from   bb.fetch2 import runfetchcmd
59from   bb.fetch2 import logger
60
61class ClearCase(FetchMethod):
62    """Class to fetch urls via 'clearcase'"""
63    def init(self, d):
64        pass
65
66    def supports(self, ud, d):
67        """
68        Check to see if a given url can be fetched with Clearcase.
69        """
70        return ud.type in ['ccrc']
71
72    def debug(self, msg):
73        logger.debug("ClearCase: %s", msg)
74
75    def urldata_init(self, ud, d):
76        """
77        init ClearCase specific variable within url data
78        """
79        ud.proto = "https"
80        if 'protocol' in ud.parm:
81            ud.proto = ud.parm['protocol']
82        if not ud.proto in ('http', 'https'):
83            raise ParameterError("Invalid protocol type", ud.url)
84
85        ud.vob = ''
86        if 'vob' in ud.parm:
87            ud.vob = ud.parm['vob']
88        else:
89            msg = ud.url+": vob must be defined so the fetcher knows what to get."
90            raise MissingParameterError('vob', msg)
91
92        if 'module' in ud.parm:
93            ud.module = ud.parm['module']
94        else:
95            ud.module = ""
96
97        ud.basecmd = d.getVar("FETCHCMD_ccrc") or "/usr/bin/env cleartool || rcleartool"
98
99        if d.getVar("SRCREV") == "INVALID":
100          raise FetchError("Set a valid SRCREV for the clearcase fetcher in your recipe, e.g. SRCREV = \"/main/LATEST\" or any other label of your choice.")
101
102        ud.label = d.getVar("SRCREV", False)
103        ud.customspec = d.getVar("CCASE_CUSTOM_CONFIG_SPEC")
104
105        ud.server     = "%s://%s%s" % (ud.proto, ud.host, ud.path)
106
107        ud.identifier = "clearcase-%s%s-%s" % ( ud.vob.replace("/", ""),
108                                                ud.module.replace("/", "."),
109                                                ud.label.replace("/", "."))
110
111        ud.viewname         = "%s-view%s" % (ud.identifier, d.getVar("DATETIME", d, True))
112        ud.csname           = "%s-config-spec" % (ud.identifier)
113        ud.ccasedir         = os.path.join(d.getVar("DL_DIR"), ud.type)
114        ud.viewdir          = os.path.join(ud.ccasedir, ud.viewname)
115        ud.configspecfile   = os.path.join(ud.ccasedir, ud.csname)
116        ud.localfile        = "%s.tar.gz" % (ud.identifier)
117
118        self.debug("host            = %s" % ud.host)
119        self.debug("path            = %s" % ud.path)
120        self.debug("server          = %s" % ud.server)
121        self.debug("proto           = %s" % ud.proto)
122        self.debug("type            = %s" % ud.type)
123        self.debug("vob             = %s" % ud.vob)
124        self.debug("module          = %s" % ud.module)
125        self.debug("basecmd         = %s" % ud.basecmd)
126        self.debug("label           = %s" % ud.label)
127        self.debug("ccasedir        = %s" % ud.ccasedir)
128        self.debug("viewdir         = %s" % ud.viewdir)
129        self.debug("viewname        = %s" % ud.viewname)
130        self.debug("configspecfile  = %s" % ud.configspecfile)
131        self.debug("localfile       = %s" % ud.localfile)
132
133        ud.localfile = os.path.join(d.getVar("DL_DIR"), ud.localfile)
134
135    def _build_ccase_command(self, ud, command):
136        """
137        Build up a commandline based on ud
138        command is: mkview, setcs, rmview
139        """
140        options = []
141
142        if "rcleartool" in ud.basecmd:
143            options.append("-server %s" % ud.server)
144
145        basecmd = "%s %s" % (ud.basecmd, command)
146
147        if command == 'mkview':
148            if not "rcleartool" in ud.basecmd:
149                # Cleartool needs a -snapshot view
150                options.append("-snapshot")
151            options.append("-tag %s" % ud.viewname)
152            options.append(ud.viewdir)
153
154        elif command == 'rmview':
155            options.append("-force")
156            options.append("%s" % ud.viewdir)
157
158        elif command == 'setcs':
159            options.append("-overwrite")
160            options.append(ud.configspecfile)
161
162        else:
163            raise FetchError("Invalid ccase command %s" % command)
164
165        ccasecmd = "%s %s" % (basecmd, " ".join(options))
166        self.debug("ccasecmd = %s" % ccasecmd)
167        return ccasecmd
168
169    def _write_configspec(self, ud, d):
170        """
171        Create config spec file (ud.configspecfile) for ccase view
172        """
173        config_spec = ""
174        custom_config_spec = d.getVar("CCASE_CUSTOM_CONFIG_SPEC", d)
175        if custom_config_spec is not None:
176            for line in custom_config_spec.split("\\n"):
177                config_spec += line+"\n"
178            bb.warn("A custom config spec has been set, SRCREV is only relevant for the tarball name.")
179        else:
180            config_spec += "element * CHECKEDOUT\n"
181            config_spec += "element * %s\n" % ud.label
182            config_spec += "load %s%s\n" % (ud.vob, ud.module)
183
184        logger.info("Using config spec: \n%s" % config_spec)
185
186        with open(ud.configspecfile, 'w') as f:
187            f.write(config_spec)
188
189    def _remove_view(self, ud, d):
190        if os.path.exists(ud.viewdir):
191            cmd = self._build_ccase_command(ud, 'rmview');
192            logger.info("cleaning up [VOB=%s label=%s view=%s]", ud.vob, ud.label, ud.viewname)
193            bb.fetch2.check_network_access(d, cmd, ud.url)
194            output = runfetchcmd(cmd, d, workdir=ud.ccasedir)
195            logger.info("rmview output: %s", output)
196
197    def need_update(self, ud, d):
198        if ("LATEST" in ud.label) or (ud.customspec and "LATEST" in ud.customspec):
199            ud.identifier += "-%s" % d.getVar("DATETIME",d, True)
200            return True
201        if os.path.exists(ud.localpath):
202            return False
203        return True
204
205    def supports_srcrev(self):
206        return True
207
208    def sortable_revision(self, ud, d, name):
209        return False, ud.identifier
210
211    def download(self, ud, d):
212        """Fetch url"""
213
214        # Make a fresh view
215        bb.utils.mkdirhier(ud.ccasedir)
216        self._write_configspec(ud, d)
217        cmd = self._build_ccase_command(ud, 'mkview')
218        logger.info("creating view [VOB=%s label=%s view=%s]", ud.vob, ud.label, ud.viewname)
219        bb.fetch2.check_network_access(d, cmd, ud.url)
220        try:
221            runfetchcmd(cmd, d)
222        except FetchError as e:
223            if "CRCLI2008E" in e.msg:
224                raise FetchError("%s\n%s\n" % (e.msg, "Call `rcleartool login` in your console to authenticate to the clearcase server before running bitbake."))
225            else:
226                raise e
227
228        # Set configspec: Setting the configspec effectively fetches the files as defined in the configspec
229        cmd = self._build_ccase_command(ud, 'setcs');
230        logger.info("fetching data [VOB=%s label=%s view=%s]", ud.vob, ud.label, ud.viewname)
231        bb.fetch2.check_network_access(d, cmd, ud.url)
232        output = runfetchcmd(cmd, d, workdir=ud.viewdir)
233        logger.info("%s", output)
234
235        # Copy the configspec to the viewdir so we have it in our source tarball later
236        shutil.copyfile(ud.configspecfile, os.path.join(ud.viewdir, ud.csname))
237
238        # Clean clearcase meta-data before tar
239
240        runfetchcmd('tar -czf "%s" .' % (ud.localpath), d, cleanup = [ud.localpath], workdir = ud.viewdir)
241
242        # Clean up so we can create a new view next time
243        self.clean(ud, d);
244
245    def clean(self, ud, d):
246        self._remove_view(ud, d)
247        bb.utils.remove(ud.configspecfile)
248