File indexing completed on 2024-04-21 03:52:27

0001 # -*- coding: utf-8 -*-
0002 #
0003 # SPDX-FileCopyrightText: 2014 Aurélien Gâteau <agateau@kde.org>
0004 #
0005 # SPDX-License-Identifier: BSD-2-Clause
0006 
0007 from fnmatch import fnmatch
0008 import logging
0009 import os
0010 import re
0011 import subprocess
0012 import shutil
0013 import sys
0014 import tempfile
0015 import requests
0016 
0017 
0018 ## @package kapidox.utils
0019 #
0020 # Multiple usage utils.
0021 #
0022 # This module contains code which is shared between depdiagram-prepare and
0023 # other components.
0024 #
0025 # Code in this dir should not import any module which is not shipped with
0026 # Python because this module is used by depdiagram-prepare, which must be able
0027 # to run on builds.kde.org, which may not have all the required dependencies.
0028 #
0029 
0030 
0031 def setup_logging():
0032     FORMAT = '%(asctime)s %(levelname)s %(message)s'
0033     logging.basicConfig(format=FORMAT, datefmt='%H:%M:%S', level=logging.DEBUG)
0034 
0035 
0036 def tolist(a):
0037     """ Return a list based on `a`. """
0038     return a if type(a) is list else [a]
0039 
0040 
0041 def serialize_name(name):
0042     """ Return a serialized name.
0043 
0044     For now it only replaces ' ' with '_' and lower the letters.
0045     """
0046     if name is not None:
0047         return '_'.join(name.lower().split(' '))
0048     else:
0049         return None
0050 
0051 
0052 def set_repopath(id):
0053     """ Return the repopath for the repo id, queried from projects.kde.org
0054 
0055     Args:
0056         id: unique KDE repo identifier
0057     """
0058     if id is None:
0059         return None
0060 
0061     try:
0062         r = requests.get('https://projects.kde.org/api/v1/identifier/' + id)
0063         return r.json()['repo']
0064     except Exception as exc:
0065         # Catch all exceptions here: whatever fails in this function should not
0066         # cause the code to fail
0067         logging.warning("Failed to get repository url for '{}' from projects.kde.org: {}".format(id, exc))
0068         # This means there is no canonical repo identifier for this repo:
0069         # generally that means that the repo was checked out into a non-
0070         # canonical pathname (e.g. kitemviews checkout out into a directory
0071         # called KItemViews, or kitemviews.git .. anything other than
0072         # kitemviews is not recognized).
0073         return None
0074 
0075 
0076 def set_maintainers(maintainer_keys, all_maintainers):
0077     """ Expend the name of the maintainers.
0078 
0079     Args:
0080         maintainer_keys: (string) Key of the dictionary where the name to expend is saved.
0081         all_maintainers: (dict of dict) Look-up table where the names and emails of
0082     the maintainers are stored.
0083 
0084     Examples:
0085         >>> maintainer_keys = ['arthur', 'toto']
0086 
0087         >>> myteam = {'arthur': {'name': 'Arthur Pendragon',
0088                                  'email': 'arthur@example.com'},
0089                       'toto': {'name': 'Toto',
0090                                'email: 'toto123@example.com'}
0091                       }
0092 
0093         >>> set_maintainers(maintainer_keys, all_maintainers)
0094     """
0095 
0096     if not maintainer_keys:
0097         maintainers = []
0098     elif isinstance(maintainer_keys, list):
0099         maintainers = map(lambda x: all_maintainers.get(x, None),
0100                           maintainer_keys)
0101     else:
0102         maintainers = [all_maintainers.get(maintainer_keys, None)]
0103 
0104     maintainers = [x for x in maintainers if x is not None]
0105     return maintainers
0106 
0107 
0108 def parse_fancyname(fw_dir):
0109     """Return the framework name for a given source dir
0110 
0111     The framework name is the name of the toplevel CMake project
0112     """
0113     cmakelists_path = os.path.join(fw_dir, "CMakeLists.txt")
0114     if not os.path.exists(cmakelists_path):
0115         for f in os.listdir(fw_dir):
0116             if ".qbs" in f and "Test" not in f:
0117                 return f[:-4]
0118         logging.error("No CMakeLists.txt in {}".format(fw_dir))
0119         return None
0120     project_re = re.compile(r"^\s*project\s*\(\s*([\w\-\_]+)", re.I | re.M)
0121     with open(cmakelists_path) as f:
0122         cmakelists_content = f.read()
0123         match = project_re.search(cmakelists_content)
0124         if match:
0125             return match.group(1)
0126 
0127     logging.error(f"Failed to find framework name: Could not find a 'project()' command in {cmakelists_path}.")
0128     return None
0129 
0130 
0131 def cache_dir():
0132     """Find/create a semi-long-term cache directory.
0133 
0134     We do not use tempdir, except as a fallback, because temporary directories
0135     are intended for files that only last for the program's execution.
0136     """
0137     cachedir = None
0138     if sys.platform == 'darwin':
0139         try:
0140             from AppKit import NSSearchPathForDirectoriesInDomains
0141             # http://developer.apple.com/DOCUMENTATION/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Functions/Reference/reference.html#//apple_ref/c/func/NSSearchPathForDirectoriesInDomains
0142             # NSApplicationSupportDirectory = 14
0143             # NSUserDomainMask = 1
0144             # True for expanding the tilde into a fully qualified path
0145             cachedir = os.path.join(
0146                 NSSearchPathForDirectoriesInDomains(14, 1, True)[0],
0147                 'KApiDox')
0148         except:
0149             pass
0150     elif os.name == "posix":
0151         if 'HOME' in os.environ and os.path.exists(os.environ['HOME']):
0152             cachedir = os.path.join(os.environ['HOME'], '.cache', 'kapidox')
0153     elif os.name == "nt":
0154         if 'APPDATA' in os.environ and os.path.exists(os.environ['APPDATA']):
0155             cachedir = os.path.join(os.environ['APPDATA'], 'KApiDox')
0156     if cachedir is None:
0157         cachedir = os.path.join(tempfile.gettempdir(), 'kapidox')
0158     if not os.path.isdir(cachedir):
0159         os.makedirs(cachedir)
0160     return cachedir
0161 
0162 
0163 def svn_export(remote, local, overwrite=False):
0164     """Wraps svn export.
0165 
0166     Args:
0167         remote:     (string) the remote url.
0168         local:      (string) the local path where to download.
0169         overwrite:  (bool) whether to overwrite `local` or not. (optional,
0170     default = False)
0171 
0172     Returns:
0173         True if success.
0174 
0175     Raises:
0176         FileNotFoundError: &nbsp;
0177         subprocess.CalledProcessError: &nbsp;
0178     """
0179     try:
0180         import svn.core
0181         import svn.client
0182         logging.debug("Using Python libsvn bindings to fetch %s", remote)
0183         ctx = svn.client.create_context()
0184         ctx.auth_baton = svn.core.svn_auth_open([])
0185 
0186         latest = svn.core.svn_opt_revision_t()
0187         latest.type = svn.core.svn_opt_revision_head
0188 
0189         svn.client.export(remote, local, latest, True, ctx)
0190     except ImportError:
0191         logging.debug("Using external svn client to fetch %s", remote)
0192         cmd = ['svn', 'export', '--quiet']
0193         if overwrite:
0194             cmd.append('--force')
0195         cmd += [remote, local]
0196         try:
0197             subprocess.check_call(cmd, stderr=subprocess.STDOUT)
0198         except subprocess.CalledProcessError as e:
0199             raise subprocess.StandardException(e.output)
0200         except FileNotFoundError as e:
0201             logging.debug("External svn client not found")
0202             return False
0203     # subversion will set the timestamp to match the server
0204     os.utime(local, None)
0205     return True
0206 
0207 
0208 def copy_dir_contents(directory, dest):
0209     """Copy the contents of a directory
0210 
0211     Args:
0212         directory: (string) the directory to copy the contents of.
0213         dest: (string) the directory to copy them into.
0214     """
0215     ignored = ['CMakeLists.txt']
0216     ignore = shutil.ignore_patterns(*ignored)
0217     for fn in os.listdir(directory):
0218         f = os.path.join(directory, fn)
0219         if os.path.isfile(f):
0220             docopy = True
0221             for i in ignored:
0222                 if fnmatch(fn, i):
0223                     docopy = False
0224                     break
0225             if docopy:
0226                 shutil.copy(f, dest)
0227         elif os.path.isdir(f):
0228             dest_f = os.path.join(dest, fn)
0229             if os.path.isdir(dest_f):
0230                 shutil.rmtree(dest_f)
0231             shutil.copytree(f, dest_f, ignore=ignore)
0232 
0233 
0234 _KAPIDOX_VERSION = None
0235 
0236 
0237 def get_kapidox_version():
0238     """Get commit id of running code if it is running from git repository.
0239 
0240     May return an empty string if it failed to extract the commit id.
0241 
0242     Assumes .git/HEAD looks like this:
0243 
0244         ref: refs/heads/master
0245 
0246     and assumes .git/refs/heads/master contains the commit id
0247     """
0248     global _KAPIDOX_VERSION
0249 
0250     if _KAPIDOX_VERSION is not None:
0251         return _KAPIDOX_VERSION
0252 
0253     _KAPIDOX_VERSION = ""
0254     bin_dir = os.path.dirname(sys.argv[0])
0255     git_dir = os.path.join(bin_dir, "..", ".git")
0256     if not os.path.isdir(git_dir):
0257         # Looks like we are not running from the git repo, exit silently
0258         return _KAPIDOX_VERSION
0259 
0260     git_HEAD = os.path.join(git_dir, "HEAD")
0261     if not os.path.isfile(git_HEAD):
0262         logging.warning(f'Getting git info failed: {git_HEAD} is not a file')
0263         return _KAPIDOX_VERSION
0264 
0265     try:
0266         line = open(git_HEAD).readline()
0267         ref_name = line.split(": ")[1].strip()
0268         with open(os.path.join(git_dir, ref_name)) as f:
0269             _KAPIDOX_VERSION = f.read().strip()
0270     except Exception as exc:
0271         # Catch all exceptions here: whatever fails in this function should not
0272         # cause the code to fail
0273         logging.warning(f'Getting git info failed: {exc}')
0274     return _KAPIDOX_VERSION
0275 
0276 
0277 def find_dot_files(dot_dir):
0278     """Returns a list of path to files ending with .dot in subdirs of `dot_dir`."""
0279     lst = []
0280     for (root, dirs, files) in os.walk(dot_dir):
0281         lst.extend([os.path.join(root, x) for x in files if x.endswith('.dot')])
0282     return lst