Skip to content

Commit 9fd87df

Browse files
authored
verify pre-existing binaries' SHA512 (#25)
resolves #24 - checks the SHA512 for binaries that are already installed - checks the SHA512 for binaries that are not already installed (freshly downloaded binaries) - add unit tests
1 parent a0ebbb3 commit 9fd87df

9 files changed

+78
-20
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ clang_tools/__pycache__
55
dist
66
clang_tools/llvm-project*
77
.coverage
8+
coverage.xml
89
htmlcov/
910
.vscode/
1011
tests/pytestdebug.log

README.rst

+13
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,19 @@ clang-tools Introduction
2020

2121
Install clang-tools binaries (clang-format, clang-tidy) with pip.
2222

23+
Features
24+
--------
25+
26+
- Binaries are statically linked for improved portability.
27+
- Binaries are checked with SHA512 checksum. This ensures:
28+
29+
1. Downloads are not corrupted.
30+
2. Old binary builds can be updated.
31+
- Installed binaries are symbolically linked for better cross-platform usage.
32+
For example (on Windows), the ``clang-tidy-13.exe`` binary executable can
33+
also be invoked with the symbolic link titled ``clang-tidy.exe``
34+
- Customizable install path.
35+
2336
Install
2437
-------
2538

clang_tools/install.py

+14-11
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,10 @@
77
import os
88
import shutil
99
import sys
10-
from pathlib import Path
11-
from pathlib import PurePath
10+
from pathlib import Path, PurePath
1211

13-
from . import install_os
14-
from . import RESET_COLOR
15-
from . import suffix
16-
from . import YELLOW
17-
from .util import download_file
12+
from . import install_os, RESET_COLOR, suffix, YELLOW
13+
from .util import download_file, verify_sha512, get_sha_checksum
1814

1915

2016
def clang_tools_binary_url(
@@ -46,14 +42,21 @@ def install_tool(tool_name: str, version: str, directory: str) -> bool:
4642
:returns: `True` if the binary had to be downloaded and installed.
4743
`False` if the binary was not downloaded but is installed in ``directory``.
4844
"""
49-
if Path(directory, f"{tool_name}-{version}{suffix}").exists():
50-
print(f"{tool_name}-{version}", "already installed")
51-
return False
45+
destination = Path(directory, f"{tool_name}-{version}{suffix}")
5246
bin_url = clang_tools_binary_url(tool_name, version)
53-
bin_name = str(PurePath(bin_url).stem)
47+
if destination.exists():
48+
print(f"{tool_name}-{version}", "already installed...")
49+
print(" checking SHA512...", end=" ")
50+
if verify_sha512(get_sha_checksum(bin_url), destination.read_bytes()):
51+
print("valid")
52+
return False
53+
print("invalid")
5454
print("downloading", tool_name, f"(version {version})")
55+
bin_name = str(PurePath(bin_url).stem)
5556
download_file(bin_url, bin_name)
5657
move_and_chmod_bin(bin_name, f"{tool_name}-{version}{suffix}", directory)
58+
if not verify_sha512(get_sha_checksum(bin_url), destination.read_bytes()):
59+
raise ValueError(f"file was corrupted during download from {bin_url}")
5760
return True
5861

5962

clang_tools/util.py

+31
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"""
77
import platform
88
import math
9+
import hashlib
910
from pathlib import Path
1011
import urllib.request
1112
from typing import Optional
@@ -63,3 +64,33 @@ def download_file(url: str, file_name: str) -> Optional[str]:
6364
file = Path(file_name)
6465
file.write_bytes(buffer)
6566
return file.as_posix()
67+
68+
69+
def get_sha_checksum(binary_url: str) -> str:
70+
"""Fetch the SHA512 checksum corresponding to the released binary.
71+
72+
:param binary_url: The URL used to download the binary.
73+
74+
:returns: A `str` containing the contents of the SHA512sum file given
75+
``binary_url``.
76+
"""
77+
with urllib.request.urlopen(
78+
binary_url.replace(".exe", "") + ".sha512sum"
79+
) as response:
80+
response: HTTPResponse
81+
return response.read(response.length).decode(encoding="utf-8")
82+
83+
84+
def verify_sha512(checksum: str, exe: bytes) -> bool:
85+
"""Verify the executable binary's SHA512 hash matches the valid checksum.
86+
87+
:param checksum: The SHA512 checksum.
88+
:param exe: The `bytes` content of the binary executable that is to be verified.
89+
90+
:returns: `True` if the ``exe`` hash matches the ``checksum`` given,
91+
otherwise `False`.
92+
"""
93+
if " " in checksum:
94+
# released checksum's include the corresponding filename (which we don't need)
95+
checksum = checksum.split(" ", 1)[0]
96+
return checksum == hashlib.sha512(exe).hexdigest()

docs/_static/extra_css.css

-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
.md-typeset table.data:not(.plain) {
2-
display:inline-block;
3-
}
4-
51
tbody .stub,
62
thead {
73
background-color: var(--md-accent-bg-color--light);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
e5ae0dc1df5b259e7ed1bf6887af9d0eeba3a659a1465999cc732e014763e5ce7d38c0bef9f36d9df1a2732ce9fac12419d8a78cc157668ad464094a8f65f066 clang-query-12_linux-amd64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
8dca8b2de3637cefd3ec739b1efd4e7430a1c83fccdca90999f1e67c14c8caabaed3df3f5fdc9a3c9ed7ab28952c9c04660c20b1f0c36bfc96d22239cd13a9f9 clang-query-12_macosx-amd64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0d7abeea833917074ced1810dbd3bcf94d0b1de15bf83dc051f65163f5e5db1ed88ee56e9000d9a67e17a74886b92a942ab14e6f27c6df04d12fdff64bf1437b *clang-query-12_windows-amd64

tests/test_util.py

+16-5
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
"""Tests related to the utility functions."""
2-
from pathlib import Path
2+
from pathlib import Path, PurePath
33
import pytest
4+
from clang_tools import install_os
45
from clang_tools.install import clang_tools_binary_url
5-
from clang_tools.util import check_install_os
6-
from clang_tools.util import download_file
6+
from clang_tools.util import check_install_os, download_file, get_sha_checksum
77

88

99
def test_check_install_os():
1010
"""Tests the return value of `check_install_os()`."""
11-
install_os = check_install_os()
12-
assert install_os in ("linux", "windows", "macosx")
11+
current_os = check_install_os()
12+
assert current_os in ("linux", "windows", "macosx")
1313

1414

1515
@pytest.mark.parametrize(
@@ -21,3 +21,14 @@ def test_download_file(monkeypatch: pytest.MonkeyPatch, tmp_path: Path, tag: str
2121
url = clang_tools_binary_url("clang-query", "12", release_tag=tag)
2222
file_name = download_file(url, "file.tar.gz")
2323
assert file_name is not None
24+
25+
26+
def test_get_sha(monkeypatch: pytest.MonkeyPatch):
27+
"""Test the get_sha() function used to fetch the
28+
releases' corresponding SHA512 checksum."""
29+
monkeypatch.chdir(PurePath(__file__).parent.as_posix())
30+
expected = Path(f"clang-query-12_{install_os}-amd64.sha512sum").read_text(
31+
encoding="utf-8"
32+
)
33+
url = clang_tools_binary_url("clang-query", "12", release_tag="master-208096c1")
34+
assert get_sha_checksum(url) == expected

0 commit comments

Comments
 (0)