From 34b55f96e1befe7b453a86bc4896d93ecb206be2 Mon Sep 17 00:00:00 2001 From: Ben Sima Date: Thu, 13 Nov 2025 18:24:15 -0500 Subject: Fix Python import detection to handle transitive dependencies detectPythonImports now recursively analyzes imported modules to find transitive dependencies, matching the behavior of detectHaskellImports. Previously it only detected direct imports, which caused build failures when Python modules had nested dependencies. - Changed signature from [Text] -> IO (Set FilePath) to Analysis -> [Text] -> IO (Set FilePath) - Added filepaths, findDeps, and onlyPython helper functions - Recursively calls analyze() on imported modules to find transitive deps - Updated tests to pass empty Analysis map --- Omni/Bild.hs | 54 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 16 deletions(-) (limited to 'Omni/Bild.hs') diff --git a/Omni/Bild.hs b/Omni/Bild.hs index 967d143..c1c4210 100755 --- a/Omni/Bild.hs +++ b/Omni/Bild.hs @@ -540,7 +540,7 @@ analyze hmap ns = case Map.lookup ns hmap of contentLines |> Meta.detectAll "#" |> \Meta.Parsed {..} -> - detectPythonImports contentLines +> \srcs -> + detectPythonImports hmap contentLines +> \srcs -> Target { builder = "python", wrapper = Nothing, @@ -818,19 +818,23 @@ detectLispImports contentLines = |> Set.fromList |> pure --- | Finds local imports. Does not recurse to find transitive imports like --- 'detectHaskellImports' does. Someday I will refactor these detection --- functions and have a common, well-performing, complete solution. -detectPythonImports :: [Text] -> IO (Set FilePath) -detectPythonImports contentLines = - contentLines - /> Text.unpack - /> Regex.match pythonImport - |> catMaybes - /> Namespace.fromPythonModule - /> Namespace.toPath - |> filterM Dir.doesPathExist - /> Set.fromList +-- | Finds local imports and recursively finds transitive imports, similar to +-- 'detectHaskellImports'. +detectPythonImports :: Analysis -> [Text] -> IO (Set FilePath) +detectPythonImports pmap contentLines = + Env.getEnv "CODEROOT" +> \root -> + contentLines + /> Text.unpack + /> Regex.match pythonImport + |> catMaybes + |> \imports -> + filepaths imports + +> \files -> + findDeps root files + +> \deps -> + (map (stripRoot root) files <> Set.toList deps) + |> Set.fromList + |> pure where -- only detects 'import x' because I don't like 'from' pythonImport :: Regex.RE Char String @@ -839,16 +843,34 @@ detectPythonImports contentLines = *> Regex.some (Regex.psym Char.isSpace) *> Regex.many (Regex.psym isModuleChar) <* Regex.many Regex.anySym + filepaths :: [String] -> IO [FilePath] + filepaths imports = + imports + |> map Namespace.fromPythonModule + |> map Namespace.toPath + |> traverse Dir.makeAbsolute + +> filterM Dir.doesFileExist + findDeps :: String -> [FilePath] -> IO (Set FilePath) + findDeps root fps = + fps + |> traverse (pure <. Namespace.fromPath root) + /> catMaybes + +> foldM analyze (onlyPython pmap) + /> Map.elems + /> map srcs + /> mconcat + onlyPython :: Analysis -> Analysis + onlyPython = Map.filterWithKey (\ns _ -> ext ns == Namespace.Py) test_detectPythonImports :: Test.Tree test_detectPythonImports = Test.group "detectPythonImports" [ Test.unit "matches import statements" <| do - set <- detectPythonImports ["import Omni.Log"] + set <- detectPythonImports mempty ["import Omni.Log"] Set.fromList ["Omni/Log.py"] @=? set, Test.unit "matches import as statements" <| do - set <- detectPythonImports ["import Omni.Log as Log"] + set <- detectPythonImports mempty ["import Omni.Log as Log"] Set.fromList ["Omni/Log.py"] @=? set ] -- cgit v1.2.3