@@ -6,7 +6,8 @@ module App.Fossa.Ficus.Analyze (
6
6
analyzeWithFicusMain ,
7
7
-- Exported for testing
8
8
singletonFicusMessage ,
9
- ) where
9
+ )
10
+ where
10
11
11
12
import App.Fossa.EmbeddedBinary (BinaryPaths , toPath , withFicusBinary )
12
13
import App.Fossa.Ficus.Types (
@@ -23,26 +24,31 @@ import App.Fossa.Ficus.Types (
23
24
FicusSnippetScanResults (.. ),
24
25
)
25
26
import App.Types (ProjectRevision (.. ))
27
+ import Control.Applicative ((<|>) )
26
28
import Control.Carrier.Diagnostics (Diagnostics )
27
- import Control.Effect.Lift (Has , Lift , sendIO )
28
- import Effect.Logger (Logger , logDebug , logInfo )
29
- import Prettyprinter (pretty )
30
-
31
- import Data.Time (getCurrentTime )
32
- import Data.Time.Format (defaultTimeLocale , formatTime )
33
-
34
29
import Control.Concurrent.Async (async , wait )
35
- import Data.Aeson (Object , decode , (.:) )
30
+ import Control.Effect.Lift (Has , Lift , sendIO )
31
+ import Data.Aeson (Object , decode , decodeStrictText , (.:) )
36
32
import Data.Aeson.Types (parseMaybe )
37
33
import Data.ByteString.Lazy qualified as BL
34
+ import Data.Conduit ((.|) )
35
+ import Data.Conduit qualified as Conduit
36
+ import Data.Conduit.Combinators qualified as CC
37
+ import Data.Conduit.List qualified as CCL
38
38
import Data.Hashable (Hashable )
39
39
import Data.Maybe (mapMaybe )
40
40
import Data.String.Conversion (ToText (toText ), toString )
41
41
import Data.Text (Text )
42
42
import Data.Text qualified as Text
43
43
import Data.Text.Encoding qualified as Text.Encoding
44
+ import Data.Time (getCurrentTime )
45
+ import Data.Time.Format (defaultTimeLocale , formatTime )
44
46
import Effect.Exec (AllowErr (Never ), Command (.. ), ExitCode (ExitSuccess ), renderCommand )
47
+ import Effect.Logger (Logger , logDebug , logInfo )
45
48
import Fossa.API.Types (ApiKey (.. ), ApiOpts (.. ))
49
+ import Path (Abs , Dir , Path , toFilePath )
50
+ import Prettyprinter (pretty )
51
+ import Srclib.Types (Locator (.. ), renderLocator )
46
52
import System.IO (Handle , hGetLine , hIsEOF )
47
53
import System.Process.Typed (
48
54
createPipe ,
@@ -55,16 +61,14 @@ import System.Process.Typed (
55
61
waitExitCode ,
56
62
withProcessWait ,
57
63
)
58
-
59
- import Path (Abs , Dir , Path , toFilePath )
60
- import Srclib.Types (Locator (.. ), renderLocator )
61
64
import Text.URI (render )
62
65
import Text.URI.Builder (PathComponent (PathComponent ), TrailingSlash (TrailingSlash ), setPath )
63
66
import Types (GlobFilter (.. ), LicenseScanPathFilters (.. ))
64
67
import Prelude
65
68
66
69
newtype CustomLicensePath = CustomLicensePath { unCustomLicensePath :: Text }
67
70
deriving (Eq , Ord , Show , Hashable )
71
+
68
72
newtype CustomLicenseTitle = CustomLicenseTitle { unCustomLicenseTitle :: Text }
69
73
deriving (Eq , Ord , Show , Hashable )
70
74
@@ -103,13 +107,12 @@ analyzeWithFicusMain ::
103
107
m (Maybe FicusSnippetScanResults )
104
108
analyzeWithFicusMain rootDir apiOpts revision filters snippetScanRetentionDays = do
105
109
logDebugWithTime " Preparing Ficus analysis configuration..."
106
- messages <- runFicus ficusConfig
110
+ ficusResults <- runFicus ficusConfig
107
111
logDebugWithTime " runFicus completed, processing results..."
108
- let ficusResults = ficusMessagesToFicusSnippetScanResults messages
109
112
case ficusResults of
110
113
Just results -> do
111
114
logInfo $ " Ficus analysis completed successfully with analysis ID: " <> pretty (ficusSnippetScanResultsAnalysisId results)
112
- logDebug $ " Found " <> pretty (length $ ficusMessageFindings messages) <> " findings from Ficus"
115
+ -- logDebug $ "Found " <> pretty (length $ ficusMessageFindings messages) <> " findings from Ficus"
113
116
Nothing -> logInfo " Ficus analysis completed but no fingerprint findings were found"
114
117
pure ficusResults
115
118
where
@@ -124,6 +127,14 @@ analyzeWithFicusMain rootDir apiOpts revision filters snippetScanRetentionDays =
124
127
, ficusConfigSnippetScanRetentionDays = snippetScanRetentionDays
125
128
}
126
129
130
+ extractAnalysisId :: FicusFinding -> Maybe Int
131
+ extractAnalysisId (FicusFinding (FicusMessageData strategy payload))
132
+ | Text. toLower strategy == " fingerprint" =
133
+ case decode (BL. fromStrict $ Text.Encoding. encodeUtf8 payload) :: Maybe Object of
134
+ Just obj -> parseMaybe (.: " analysis_id" ) obj
135
+ Nothing -> Nothing
136
+ extractAnalysisId _ = Nothing
137
+
127
138
ficusMessagesToFicusSnippetScanResults :: FicusMessages -> Maybe FicusSnippetScanResults
128
139
ficusMessagesToFicusSnippetScanResults messages =
129
140
let isFingerprintStrategy :: FicusFinding -> Bool
@@ -148,7 +159,7 @@ runFicus ::
148
159
, Has Logger sig m
149
160
) =>
150
161
FicusConfig ->
151
- m FicusMessages
162
+ m ( Maybe FicusSnippetScanResults )
152
163
runFicus ficusConfig = do
153
164
logDebugWithTime " About to extract Ficus binary..."
154
165
withFicusBinary $ \ bin -> do
@@ -166,7 +177,7 @@ runFicus ficusConfig = do
166
177
167
178
logInfo $ " Running Ficus analysis on " <> pretty (toFilePath $ ficusConfigRootDir ficusConfig)
168
179
logDebugWithTime " Starting Ficus process..."
169
- (messages , exitCode, stdErrLines) <- sendIO $ withProcessWait processConfig $ \ p -> do
180
+ (result , exitCode, stdErrLines) <- sendIO $ withProcessWait processConfig $ \ p -> do
170
181
getCurrentTime >>= \ now -> putStrLn $ " [TIMING " ++ formatTime defaultTimeLocale " %H:%M:%S.%3q" now ++ " ] Ficus process started, beginning stream processing..."
171
182
let stdoutHandle = getStdout p
172
183
let stderrHandle = getStderr p
@@ -187,45 +198,45 @@ runFicus ficusConfig = do
187
198
logInfo $ pretty (Text. unlines stdErrLines)
188
199
logInfo " \n ==== END Ficus STDERR ====\n "
189
200
else logInfo " [Ficus] Ficus exited successfully"
190
- logDebug $
191
- " [Ficus] Ficus returned "
192
- <> pretty (length $ ficusMessageErrors messages)
193
- <> " errors, "
194
- <> pretty (length $ ficusMessageDebugs messages)
195
- <> " debug messages, "
196
- <> pretty (length $ ficusMessageFindings messages)
197
- <> " findings"
201
+ -- logDebug $
202
+ -- "[Ficus] Ficus returned "
203
+ -- <> pretty (length $ ficusMessageErrors messages)
204
+ -- <> " errors, "
205
+ -- <> pretty (length $ ficusMessageDebugs messages)
206
+ -- <> " debug messages, "
207
+ -- <> pretty (length $ ficusMessageFindings messages)
208
+ -- <> " findings"
198
209
199
- pure messages
210
+ pure result
200
211
where
201
- streamFicusOutput :: Handle -> IO FicusMessages
202
- streamFicusOutput handle = do
203
- let loop acc = do
204
- eof <- hIsEOF handle
205
- if eof
206
- then do
207
- putStrLn " [DEBUG] Reached end of Ficus output stream "
208
- pure acc
209
- else do
210
- line <- hGetLine handle
211
- let lineBS = BL. fromStrict $ Text.Encoding. encodeUtf8 $ toText line
212
- case decode lineBS of
213
- Just message -> do
214
- -- Log messages as they come, with timestamps
215
- now <- getCurrentTime
216
- let timestamp = formatTime defaultTimeLocale " %H:%M:%S.%3q " now
217
- case message of
218
- FicusMessageError err ->
219
- putStrLn $ " [" ++ timestamp ++ " ] ERROR " <> toString (displayFicusError err)
220
- FicusMessageDebug dbg ->
221
- putStrLn $ " [ " ++ timestamp ++ " ] DEBUG " <> toString (displayFicusDebug dbg)
222
- FicusMessageFinding finding ->
223
- putStrLn $ " [ " ++ timestamp ++ " ] FINDING " <> toString (displayFicusFinding finding)
224
- let newMessage = singletonFicusMessage message
225
- loop (acc <> newMessage )
226
- Nothing -> do
227
- loop acc
228
- loop mempty
212
+ currentTimeStamp :: IO String
213
+ currentTimeStamp = do
214
+ now <- getCurrentTime
215
+ pure . formatTime defaultTimeLocale " %H:%M:%S.%3q " $ now
216
+
217
+ streamFicusOutput :: Handle -> IO ( Maybe FicusSnippetScanResults )
218
+ streamFicusOutput handle =
219
+ Conduit. runConduit $
220
+ CC. sourceHandle handle
221
+ .| CC. decodeUtf8Lenient
222
+ .| CC. linesUnbounded
223
+ .| CCL. mapMaybe decodeStrictText
224
+ .| CC. foldM
225
+ ( \ acc message -> do
226
+ -- Log messages as they come, with timestamps
227
+ timestamp <- currentTimeStamp
228
+ case message of
229
+ FicusMessageError err -> do
230
+ putStrLn $ " [" ++ timestamp ++ " ] ERROR " <> toString (displayFicusError err)
231
+ pure acc
232
+ FicusMessageDebug dbg -> do
233
+ putStrLn $ " [ " ++ timestamp ++ " ] DEBUG " <> toString (displayFicusDebug dbg)
234
+ pure acc
235
+ FicusMessageFinding finding -> do
236
+ putStrLn $ " [ " ++ timestamp ++ " ] FINDING " <> toString (displayFicusFinding finding )
237
+ pure $ acc <|> ( FicusSnippetScanResults <$> extractAnalysisId finding)
238
+ )
239
+ Nothing
229
240
230
241
consumeStderr :: Handle -> IO [Text ]
231
242
consumeStderr handle = do
0 commit comments