Skip to content

Commit

Permalink
Use theJUVIX_LLVM_DIST_PATH environment variable to search for the …
Browse files Browse the repository at this point in the history
…clang executable (#2152)

If set, `JUVIX_LLVM_DIST_PATH` should point to the root of a LLVM
installation, i.e clang should be present
in`$JUVIX_LLVM_DIST_PATH`/bin/clang.

If `JUVIX_LLVM_DIST_PATH` is not set, or `clang` is not available there
then the system PATH is used instead, (this is the current behaviour).

The `juvix doctor` clang checks use the same logic as `juvix compile` to
find and check the `clang` executable.

To help with debugging the clang location, this PR also adds `juvix
doctor --verbose` which prints the location of the `clang` executable
and whether it was found using the system PATH or the
JUVIX_LLVM_DIST_PATH environment variable:

```
juvix doctor --verbose
> Checking for clang...
  | Found clang at "/Users/paul/.local/share/juvix/llvmbox/bin/clang" using JUVIX_LLVM_DIST_PATH environment variable
```

or

```
juvix doctor --verbose
> Checking for clang...
  | Found clang at "/Users/paul/.local/bin/clang" using system PATH
```

* Closes #2133
  • Loading branch information
paulcadman authored Jun 1, 2023
1 parent 757b4ed commit 40e6648
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 26 deletions.
76 changes: 53 additions & 23 deletions app/Commands/Doctor.hs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module Commands.Doctor where

import Commands.Doctor.Options
import Commands.Extra.Compile
import Data.Aeson
import Data.Aeson.TH
import Juvix.Extra.Version qualified as V
Expand Down Expand Up @@ -64,26 +65,34 @@ heading = log . ("> " <>)
warning :: (Member Log r) => Text -> Sem r ()
warning = log . (" ! " <>)

info :: (Member Log r) => Text -> Sem r ()
info = log . (" | " <>)

type DoctorEff = '[Log, Embed IO]

checkCmdOnPath :: (Members DoctorEff r) => String -> [Text] -> Sem r ()
checkCmdOnPath cmd errMsg =
whenM (isNothing <$> findExecutable (relFile cmd)) (mapM_ warning errMsg)

checkClangTargetSupported :: (Members DoctorEff r) => String -> [Text] -> Sem r ()
checkClangTargetSupported target errMsg = do
-- | Check that wasm-ld exists in the same LLVM distribution as the clang command
checkWasmLd :: (Members DoctorEff r) => Path Abs File -> [Text] -> Sem r ()
checkWasmLd clangPath errMsg =
unlessM (isExecutable (parent clangPath <//> $(mkRelFile "wasm-ld"))) (mapM_ warning errMsg)

checkClangTargetSupported :: (Members DoctorEff r) => Path Abs File -> String -> [Text] -> Sem r ()
checkClangTargetSupported clangPath target errMsg = do
(code, _, _) <-
embed
( P.readProcessWithExitCode
"clang"
(toFilePath clangPath)
["-target", target, "--print-supported-cpus"]
""
)
unless (code == ExitSuccess) (mapM_ warning errMsg)

checkClangVersion :: (Members DoctorEff r) => Integer -> [Text] -> Sem r ()
checkClangVersion expectedVersion errMsg = do
versionString <- embed (P.readProcess "clang" ["-dumpversion"] "")
checkClangVersion :: (Members DoctorEff r) => Path Abs File -> Integer -> [Text] -> Sem r ()
checkClangVersion clangPath expectedVersion errMsg = do
versionString <- embed (P.readProcess (toFilePath clangPath) ["-dumpversion"] "")
case headMay (splitOn "." versionString) >>= readMaybe of
Just majorVersion -> unless (majorVersion >= expectedVersion) (mapM_ warning errMsg)
Nothing -> warning "Could not determine clang version"
Expand All @@ -110,29 +119,50 @@ checkVersion = do
Nothing -> warning "Tag name is not present in release JSON from Github API"
Nothing -> warning "Network error when fetching data from Github API"

documentedCheck ::
([Text] -> Sem r ()) -> DocumentedWarning -> Sem r ()
documentedCheck check w = check msg
renderDocumentedWarning :: DocumentedWarning -> [Text]
renderDocumentedWarning w = [dmsg ^. documentedMessageMessage, dmsg ^. documentedMessageUrl]
where
dmsg :: DocumentedMessage
dmsg = documentedMessage w
msg :: [Text]
msg = [dmsg ^. documentedMessageMessage, dmsg ^. documentedMessageUrl]

checkClang :: (Members DoctorEff r) => Sem r ()
checkClang = do
documentedCheck ::
([Text] -> Sem r ()) -> DocumentedWarning -> Sem r ()
documentedCheck check w = check (renderDocumentedWarning w)

findClangPath :: Members DoctorEff r => Sem r (Maybe ClangPath)
findClangPath = findClang

checkClang :: forall r. Members DoctorEff r => Bool -> Sem r ()
checkClang printVerbose = do
heading "Checking for clang..."
documentedCheck (checkCmdOnPath "clang") NoClang
heading "Checking clang version..."
documentedCheck (checkClangVersion minimumClangVersion) OldClang
heading "Checking for wasm-ld..."
documentedCheck (checkCmdOnPath "wasm-ld") NoWasmLd
heading "Checking that clang supports wasm32..."
documentedCheck (checkClangTargetSupported "wasm32") NoWasm32Target
heading "Checking that clang supports wasm32-wasi..."
documentedCheck (checkClangTargetSupported "wasm32-wasi") NoWasm32WasiTarget
clangPath <- findClangPath
case clangPath of
Just cp -> when printVerbose (printVerbosePath cp)
Nothing -> mapM_ warning (renderDocumentedWarning NoClang)

mapM_ checkClangProperties (extractClangPath <$> clangPath)

heading "Checking that WASI_SYSROOT_PATH is set..."
documentedCheck (checkEnvVarSet "WASI_SYSROOT_PATH") NoSysroot
where
checkClangProperties :: Path Abs File -> Sem r ()
checkClangProperties p = do
heading "Checking clang version..."
documentedCheck (checkClangVersion p minimumClangVersion) OldClang
heading "Checking for wasm-ld..."
documentedCheck (checkWasmLd p) NoWasmLd
heading "Checking that clang supports wasm32..."
documentedCheck (checkClangTargetSupported p "wasm32") NoWasm32Target
heading "Checking that clang supports wasm32-wasi..."
documentedCheck (checkClangTargetSupported p "wasm32-wasi") NoWasm32WasiTarget

baseClangMsg :: Path Abs File -> Text
baseClangMsg p = "Found clang at " <> show p

printVerbosePath :: ClangPath -> Sem r ()
printVerbosePath = \case
ClangEnvVarPath p -> info (baseClangMsg p <> (" using " <> pack llvmDistEnvironmentVar <> " environment variable"))
ClangSystemPath p -> info (baseClangMsg p <> (" using system PATH"))

checkWasmer :: (Members DoctorEff r) => Sem r ()
checkWasmer = do
Expand All @@ -141,6 +171,6 @@ checkWasmer = do

runCommand :: (Members DoctorEff r) => DoctorOptions -> Sem r ()
runCommand opts = do
checkClang
checkClang (opts ^. doctorVerbose)
checkWasmer
unless (opts ^. doctorOffline) checkVersion
11 changes: 9 additions & 2 deletions app/Commands/Doctor/Options.hs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ module Commands.Doctor.Options where

import CommonOptions

newtype DoctorOptions = DoctorOptions
{ _doctorOffline :: Bool
data DoctorOptions = DoctorOptions
{ _doctorOffline :: Bool,
_doctorVerbose :: Bool
}
deriving stock (Data)

Expand All @@ -14,4 +15,10 @@ parseDoctorOptions = do
( long "offline"
<> help "Run the doctor offline"
)
_doctorVerbose <-
switch
( long "verbose"
<> short 'v'
<> help "Print verbose output"
)
pure DoctorOptions {..}
51 changes: 50 additions & 1 deletion app/Commands/Extra/Compile.hs
Original file line number Diff line number Diff line change
Expand Up @@ -208,18 +208,67 @@ wasiArgs buildDir o outfile inputFile sysrootPath =
| otherwise -> []
)

findClangOnPath :: Member (Embed IO) r => Sem r (Maybe (Path Abs File))
findClangOnPath = findExecutable $(mkRelFile "clang")

findClangUsingEnvVar :: forall r. Member (Embed IO) r => Sem r (Maybe (Path Abs File))
findClangUsingEnvVar = do
p <- clangBinPath
join <$> mapM checkExecutable p
where
checkExecutable :: Path Abs File -> Sem r (Maybe (Path Abs File))
checkExecutable p = whenMaybeM (embed @IO (isExecutable p)) (return p)

clangBinPath :: Sem r (Maybe (Path Abs File))
clangBinPath = fmap (<//> $(mkRelFile "bin/clang")) <$> llvmDistPath

llvmDistPath :: Sem r (Maybe (Path Abs Dir))
llvmDistPath = do
p <- embed (lookupEnv llvmDistEnvironmentVar)
embed @IO (mapM parseAbsDir p)

data ClangPath
= ClangSystemPath (Path Abs File)
| ClangEnvVarPath (Path Abs File)

extractClangPath :: ClangPath -> Path Abs File
extractClangPath = \case
ClangSystemPath p -> p
ClangEnvVarPath p -> p

--- Try searching clang JUVIX_LLVM_DIST_PATH. Otherwise use the PATH
findClang :: Member (Embed IO) r => Sem r (Maybe ClangPath)
findClang = do
envVarPath <- findClangUsingEnvVar
case envVarPath of
Just p -> return (Just (ClangEnvVarPath p))
Nothing -> (fmap . fmap) ClangSystemPath findClangOnPath

runClang ::
forall r.
(Members '[Embed IO, Error Text] r) =>
[String] ->
Sem r ()
runClang args = do
(exitCode, _, err) <- embed (P.readProcessWithExitCode "clang" args "")
cp <- clangBinPath
(exitCode, _, err) <- embed (P.readProcessWithExitCode cp args "")
case exitCode of
ExitSuccess -> return ()
_ -> throw (pack err)
where
clangBinPath :: Sem r String
clangBinPath = do
p <- findClang
maybe (throw clangNotFoundErr) (return . toFilePath . extractClangPath) p

clangNotFoundErr :: Text
clangNotFoundErr = "Error: The clang executable was not found. Please install the LLVM toolchain"

debugClangOptimizationLevel :: Int
debugClangOptimizationLevel = 1

defaultClangOptimizationLevel :: Int
defaultClangOptimizationLevel = 1

llvmDistEnvironmentVar :: String
llvmDistEnvironmentVar = "JUVIX_LLVM_DIST_PATH"
4 changes: 4 additions & 0 deletions src/Juvix/Prelude/Path.hs
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,7 @@ parents = go [] . parent

withTempDir' :: (MonadIO m, MonadMask m) => (Path Abs Dir -> m a) -> m a
withTempDir' = withSystemTempDir "tmp"

-- | 'pure True' if the file exists and is executable, 'pure False' otherwise
isExecutable :: MonadIO m => Path b File -> m Bool
isExecutable f = doesFileExist f &&^ (executable <$> getPermissions f)
37 changes: 37 additions & 0 deletions tests/smoke/Commands/version-help-doctor.smoke.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,41 @@ tests:
> Checking that WASI_SYSROOT_PATH is set...
> Checking for wasmer...
- name: cli-cmd-doctor-verbose-system
command:
- juvix
- doctor
- --verbose
- --offline
exit-status: 0
stdout:
matches:
regex: |-
Found clang(.*?)using system PATH
- name: cli-cmd-doctor-verbose-envvar
command:
shell:
- bash
script: |
CLANG_PATH=`which clang`
export JUVIX_LLVM_DIST_PATH=${CLANG_PATH%$'/bin/clang'}
juvix doctor --verbose --offline
exit-status: 0
stdout:
matches:
regex: |-
Found clang(.*?)using JUVIX_LLVM_DIST_PATH
- name: cli-cmd-doctor-verbose-envvar-system-fallback
command:
shell:
- bash
script: |
export JUVIX_LLVM_DIST_PATH=/tmp
juvix doctor --verbose --offline
exit-status: 0
stdout:
matches:
regex: |-
Found clang(.*?)using system PATH

0 comments on commit 40e6648

Please sign in to comment.