5
5
The module that performs the installation of clang-tools.
6
6
"""
7
7
import os
8
+ from pathlib import Path , PurePath
9
+ import re
8
10
import shutil
11
+ import subprocess
9
12
import sys
10
- from pathlib import Path , PurePath
13
+ from typing import Optional , Union
11
14
12
15
from . import install_os , RESET_COLOR , suffix , YELLOW
13
16
from .util import download_file , verify_sha512 , get_sha_checksum
14
17
15
18
19
+ #: This pattern is designed to match only the major version number.
20
+ RE_PARSE_VERSION = re .compile (rb"version\s([\d\.]+)" , re .MULTILINE )
21
+
22
+
23
+ def is_installed (tool_name : str , version : str ) -> Optional [Path ]:
24
+ """Detect if the specified tool is installed.
25
+
26
+ :param tool_name: The name of the specified tool.
27
+ :param version: The specific version to expect.
28
+
29
+ :returns: The path to the detected tool (if found), otherwise `None`.
30
+ """
31
+ version_tuple = version .split ("." )
32
+ ver_major = version_tuple [0 ]
33
+ if len (version_tuple ) < 3 :
34
+ # append minor and patch version numbers if not specified
35
+ version_tuple += (0 ,) * (3 - len (version_tuple ))
36
+ exe_name = (
37
+ f"{ tool_name } " + (f"-{ ver_major } " if install_os != "windows" else "" ) + suffix
38
+ )
39
+ try :
40
+ result = subprocess .run (
41
+ [exe_name , "--version" ], capture_output = True , check = True
42
+ )
43
+ except (FileNotFoundError , subprocess .CalledProcessError ):
44
+ return None # tool is not installed
45
+ ver_num = RE_PARSE_VERSION .search (result .stdout )
46
+ print (
47
+ f"Found a installed version of { tool_name } :" ,
48
+ ver_num .groups (0 )[0 ].decode (encoding = "utf-8" ),
49
+ end = " "
50
+ )
51
+ path = shutil .which (exe_name ) # find the installed binary
52
+ if path is None :
53
+ print () # print end-of-line
54
+ return None # failed to locate the binary
55
+ path = Path (path ).resolve ()
56
+ print ("at" , str (path ))
57
+ if (
58
+ ver_num is None
59
+ or ver_num .groups (0 )[0 ].decode (encoding = "utf-8" ).split ("." ) != version_tuple
60
+ ):
61
+ return None # version is unknown or not the desired major release
62
+ return path
63
+
64
+
16
65
def clang_tools_binary_url (
17
66
tool : str , version : str , release_tag : str = "master-208096c1"
18
67
) -> str :
@@ -107,7 +156,11 @@ def move_and_chmod_bin(old_bin_name: str, new_bin_name: str, install_dir: str) -
107
156
108
157
109
158
def create_sym_link (
110
- tool_name : str , version : str , install_dir : str , overwrite : bool = False
159
+ tool_name : str ,
160
+ version : str ,
161
+ install_dir : str ,
162
+ overwrite : bool = False ,
163
+ target : Path = None ,
111
164
) -> bool :
112
165
"""Create a symlink to the installed binary that
113
166
doesn't have the version number appended.
@@ -116,11 +169,19 @@ def create_sym_link(
116
169
:param version: The version of the clang-tool to symlink.
117
170
:param install_dir: The installation directory to create the symlink in.
118
171
:param overwrite: A flag to indicate if an existing symlink should be overwritten.
172
+ :param target: The target executable's path and name for which to create a symlink
173
+ to. If this argument is not specified (or is `None`), then the target's path and
174
+ name is constructed from the ``tool_name``, ``version``, and ``install_dir``
175
+ parameters.
119
176
120
177
:returns: A `bool` describing if the symlink was created.
121
178
"""
122
- link = Path (install_dir ) / (tool_name + suffix )
123
- target = Path (install_dir ) / f"{ tool_name } -{ version } { suffix } "
179
+ link_root_path = Path (install_dir )
180
+ if not link_root_path .exists ():
181
+ link_root_path .mkdir (parents = True )
182
+ link = link_root_path / (tool_name + suffix )
183
+ if target is None :
184
+ target = link_root_path / f"{ tool_name } -{ version } { suffix } "
124
185
if link .exists ():
125
186
if not link .is_symlink ():
126
187
print (
@@ -146,7 +207,7 @@ def create_sym_link(
146
207
except OSError as exc : # pragma: no cover
147
208
print (
148
209
"Encountered an error when trying to create the symbolic link:" ,
149
- exc .strerror ,
210
+ "; " . join ([ x for x in exc .args if isinstance ( x , str )]) ,
150
211
sep = "\n " ,
151
212
)
152
213
if install_os == "windows" :
@@ -205,6 +266,10 @@ def install_clang_tools(
205
266
f"directory is not in your environment variable PATH.{ RESET_COLOR } " ,
206
267
)
207
268
for tool_name in ("clang-format" , "clang-tidy" ):
208
- install_tool (tool_name , version , install_dir , no_progress_bar )
209
- # `install_tool()` guarantees that the binary exists now
210
- create_sym_link (tool_name , version , install_dir , overwrite ) # pragma: no cover
269
+ native_bin = is_installed (tool_name , version )
270
+ if native_bin is None : # (not already installed)
271
+ # `install_tool()` guarantees that the binary exists now
272
+ install_tool (tool_name , version , install_dir , no_progress_bar )
273
+ create_sym_link ( # pragma: no cover
274
+ tool_name , version , install_dir , overwrite , native_bin
275
+ )
0 commit comments