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: 0177 subprocess.CalledProcessError: 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