Skip to content

Commit aeee5f6

Browse files
shenxianpeng2bndy5
andauthored
Use native binaries on Unix-like OS (#33)
* address #32 * resolve symlink target before making symlink * print resolved path to installed binary * modify CI workflow * only print str params of OSError * use bash shell for a step in the CI * remove temp call to `ls -all` in CI * ensure symlink's root path exists fix typo * use strict version behavior Co-authored-by: Brendan <[email protected]>
1 parent 5a1cae0 commit aeee5f6

File tree

2 files changed

+78
-10
lines changed

2 files changed

+78
-10
lines changed

.github/workflows/python-test.yml

+5-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ name: Test
55

66
on:
77
push:
8-
branches: [ "main" ]
8+
# branches: [ "main" ]
99
paths:
1010
- '**.py'
1111
- '.github/workflows/python-test.yml'
@@ -80,8 +80,11 @@ jobs:
8080
run: pip install dist/*.whl
8181

8282
- name: Install clang-tools
83+
run: clang-tools --install ${{ matrix.version }}
84+
85+
- name: show path of binaries
86+
shell: bash
8387
run: |
84-
clang-tools --install ${{ matrix.version }}
8588
which clang-format-${{ matrix.version }}
8689
which clang-tidy-${{ matrix.version }}
8790

clang_tools/install.py

+73-8
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,63 @@
55
The module that performs the installation of clang-tools.
66
"""
77
import os
8+
from pathlib import Path, PurePath
9+
import re
810
import shutil
11+
import subprocess
912
import sys
10-
from pathlib import Path, PurePath
13+
from typing import Optional, Union
1114

1215
from . import install_os, RESET_COLOR, suffix, YELLOW
1316
from .util import download_file, verify_sha512, get_sha_checksum
1417

1518

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+
1665
def clang_tools_binary_url(
1766
tool: str, version: str, release_tag: str = "master-208096c1"
1867
) -> str:
@@ -107,7 +156,11 @@ def move_and_chmod_bin(old_bin_name: str, new_bin_name: str, install_dir: str) -
107156

108157

109158
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,
111164
) -> bool:
112165
"""Create a symlink to the installed binary that
113166
doesn't have the version number appended.
@@ -116,11 +169,19 @@ def create_sym_link(
116169
:param version: The version of the clang-tool to symlink.
117170
:param install_dir: The installation directory to create the symlink in.
118171
: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.
119176
120177
:returns: A `bool` describing if the symlink was created.
121178
"""
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}"
124185
if link.exists():
125186
if not link.is_symlink():
126187
print(
@@ -146,7 +207,7 @@ def create_sym_link(
146207
except OSError as exc: # pragma: no cover
147208
print(
148209
"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)]),
150211
sep="\n ",
151212
)
152213
if install_os == "windows":
@@ -205,6 +266,10 @@ def install_clang_tools(
205266
f"directory is not in your environment variable PATH.{RESET_COLOR}",
206267
)
207268
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

Comments
 (0)