Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

slither-doctor: PATH checks #1550

Merged
merged 11 commits into from
Jan 9, 2023
87 changes: 87 additions & 0 deletions .github/workflows/doctor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
---
name: CI

defaults:
run:
shell: bash

on:
workflow_dispatch:
pull_request:
paths:
- 'slither/tools/doctor/**'
- '.github/workflows/doctor.yml'

jobs:
slither-doctor:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: ["ubuntu-latest", "windows-2022"]
python: ["3.8", "3.9", "3.10", "3.11"]
exclude:
# strange failure
- os: windows-2022
python: 3.8
steps:
- uses: actions/checkout@v3

- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python }}

- name: Try system-wide Slither
run: |
echo "::group::Install slither"
pip3 install .
echo "::endgroup::"

# escape cwd so python doesn't pick up local module
cd /

echo "::group::Via module"
python3 -m slither.tools.doctor .
echo "::endgroup::"

echo "::group::Via binary"
slither-doctor .
echo "::endgroup::"

- name: Try user Slither
run: |
echo "::group::Install slither"
pip3 install --user .
echo "::endgroup::"

# escape cwd so python doesn't pick up local module
cd /

echo "::group::Via module"
python3 -m slither.tools.doctor .
echo "::endgroup::"

echo "::group::Via binary"
slither-doctor .
echo "::endgroup::"

- name: Try venv Slither
run: |
echo "::group::Install slither"
python3 -m venv venv
source venv/bin/activate || source venv/Scripts/activate
hash -r
pip3 install .
echo "::endgroup::"

# escape cwd so python doesn't pick up local module
cd /

echo "::group::Via module"
python3 -m slither.tools.doctor .
echo "::endgroup::"

echo "::group::Via binary"
slither-doctor .
echo "::endgroup::"
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
packages=find_packages(),
python_requires=">=3.8",
install_requires=[
"packaging",
"prettytable>=0.7.2",
"pycryptodome>=3.4.6",
# "crytic-compile>=0.2.4",
Expand Down
5 changes: 5 additions & 0 deletions slither/tools/doctor/__main__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import argparse
import logging
import sys

from crytic_compile import cryticparser

Expand All @@ -25,6 +27,9 @@ def parse_args() -> argparse.Namespace:


def main():
# log on stdout to keep output in order
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG, force=True)

args = parse_args()
kwargs = vars(args)

Expand Down
2 changes: 2 additions & 0 deletions slither/tools/doctor/checks/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import Callable, List
from dataclasses import dataclass

from slither.tools.doctor.checks.paths import check_slither_path
from slither.tools.doctor.checks.platform import compile_project, detect_platform
from slither.tools.doctor.checks.versions import show_versions

Expand All @@ -12,6 +13,7 @@ class Check:


ALL_CHECKS: List[Check] = [
Check("PATH configuration", check_slither_path),
Check("Software versions", show_versions),
Check("Project platform", detect_platform),
Check("Project compilation", compile_project),
Expand Down
85 changes: 85 additions & 0 deletions slither/tools/doctor/checks/paths.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
from pathlib import Path
from typing import List, Optional, Tuple
import shutil
import sys
import sysconfig

from slither.utils.colors import yellow, green, red


def path_is_relative_to(path: Path, relative_to: Path) -> bool:
"""
Check if a path is relative to another one.

Compatibility wrapper for Path.is_relative_to
"""
if sys.version_info >= (3, 9, 0):
return path.is_relative_to(relative_to)

path_parts = path.resolve().parts
relative_to_parts = relative_to.resolve().parts

if len(path_parts) < len(relative_to_parts):
return False

for (a, b) in zip(path_parts, relative_to_parts):
if a != b:
return False

return True


def check_path_config(name: str) -> Tuple[bool, Optional[Path], List[Path]]:
"""
Check if a given Python binary/script is in PATH.
:return: Returns if the binary on PATH corresponds to this installation,
its path (if present), and a list of possible paths where this
binary might be found.
"""
binary_path = shutil.which(name)
possible_paths = []

for scheme in sysconfig.get_scheme_names():
script_path = Path(sysconfig.get_path("scripts", scheme))
purelib_path = Path(sysconfig.get_path("purelib", scheme))
script_binary_path = shutil.which(name, path=script_path)
if script_binary_path is not None:
possible_paths.append((script_path, purelib_path))

binary_here = False
if binary_path is not None:
binary_path = Path(binary_path)
this_code = Path(__file__)
this_binary = list(filter(lambda x: path_is_relative_to(this_code, x[1]), possible_paths))
binary_here = len(this_binary) > 0 and all(
path_is_relative_to(binary_path, script) for script, _ in this_binary
)

return binary_here, binary_path, list(set(script for script, _ in possible_paths))


def check_slither_path(**_kwargs) -> None:
binary_here, binary_path, possible_paths = check_path_config("slither")
show_paths = False

if binary_path:
print(green(f"`slither` found in PATH at `{binary_path}`."))
if binary_here:
print(green("Its location matches this slither-doctor installation."))
else:
print(
yellow(
"This path does not correspond to this slither-doctor installation.\n"
+ "Double-check the order of directories in PATH if you have several Slither installations."
)
)
show_paths = True
else:
print(red("`slither` was not found in PATH."))
show_paths = True

if show_paths:
print()
print("Consider adding one of the following directories to PATH:")
for path in possible_paths:
print(f" * {path}")
10 changes: 6 additions & 4 deletions slither/tools/doctor/checks/versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@
from typing import Optional
import urllib

from packaging.version import parse, LegacyVersion, Version
from packaging.version import parse, Version

from slither.utils.colors import yellow, green


def get_installed_version(name: str) -> Optional[LegacyVersion | Version]:
def get_installed_version(name: str) -> Optional[Version]:
try:
return parse(metadata.version(name))
except metadata.PackageNotFoundError:
return None


def get_github_version(name: str) -> Optional[LegacyVersion | Version]:
def get_github_version(name: str) -> Optional[Version]:
try:
with urllib.request.urlopen(
f"https://api.github.com/repos/crytic/{name}/releases/latest"
Expand Down Expand Up @@ -45,7 +45,9 @@ def show_versions(**_kwargs) -> None:

for name, (installed, latest) in versions.items():
color = yellow if name in outdated else green
print(f"{name + ':':<16}{color(installed or 'N/A'):<16} (latest is {latest or 'Unknown'})")
print(
f"{name + ':':<16}{color(str(installed) or 'N/A'):<16} (latest is {str(latest) or 'Unknown'})"
)

if len(outdated) > 0:
print()
Expand Down