diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4510eea..9a7d927 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,23 +12,25 @@ defaults: jobs: cabal: - name: ${{ matrix.os }} / GHC ${{ matrix.ghc }} / ${{ matrix.python-version }} + name: ${{ matrix.os }} / GHC ${{ matrix.ghc }} / ${{ matrix.python-version }} / ${{ matrix.cabal-flags }} runs-on: ${{ matrix.os }} strategy: matrix: include: - - { cabal: "3.14", os: ubuntu-latest, ghc: "9.2.8", python-version: "3.12" } - { cabal: "3.14", os: ubuntu-latest, ghc: "9.4.8", python-version: "3.12" } - { cabal: "3.14", os: ubuntu-latest, ghc: "9.6.6", python-version: "3.12" } - { cabal: "3.14", os: ubuntu-latest, ghc: "9.8.4", python-version: "3.12" } - { cabal: "3.14", os: ubuntu-latest, ghc: "9.10.2", python-version: "3.12" } - { cabal: "3.14", os: ubuntu-latest, ghc: "9.12.2", python-version: "3.12" } + - { cabal: "3.14", os: ubuntu-latest, ghc: "9.14.1", python-version: "3.12" } # Scan over supported python versions - { cabal: "3.14", os: ubuntu-latest, ghc: "9.10.2", python-version: "3.10" } - { cabal: "3.14", os: ubuntu-latest, ghc: "9.10.2", python-version: "3.11" } - { cabal: "3.14", os: ubuntu-latest, ghc: "9.10.2", python-version: "3.12" } - { cabal: "3.14", os: ubuntu-latest, ghc: "9.10.2", python-version: "3.13" } - { cabal: "3.14", os: ubuntu-latest, ghc: "9.10.2", python-version: "3.14" } + # Build with python3-config instead of pkg-config + - { cabal: "3.14", os: ubuntu-latest, ghc: "9.10.2", python-version: "3.12", cabal-flags: "-fpython3-config" } fail-fast: false steps: # ---------------- @@ -58,6 +60,8 @@ jobs: python -V pkg-config python3-embed --cflags pkg-config python3-embed --libs + python3-config --cflags + python3-config --libs # ---------------- - name: Make sdist run: | @@ -77,7 +81,7 @@ jobs: run: | if [ "${{ matrix.skip-test }}" == "" ]; then FLAG_TEST=--enable-test; fi if [ "${{ matrix.skip-bench }}" == "" ]; then FLAG_BENCH=--enable-benchmarks; fi - cabal configure $FLAG_TEST $FLAG_BENCH + cabal configure $FLAG_TEST $FLAG_BENCH ${{ matrix.cabal-flags }} cabal build all --write-ghc-environment-files=always # ---------------- - name: Test diff --git a/Setup.hs b/Setup.hs new file mode 100644 index 0000000..3b355fa --- /dev/null +++ b/Setup.hs @@ -0,0 +1,79 @@ +{-# LANGUAGE CPP #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE OverloadedRecordDot #-} +{-# LANGUAGE OverloadedStrings #-} +import Data.Either + +import Distribution.Simple +import Distribution.Simple.Setup +import Distribution.Simple.LocalBuildInfo +import Distribution.PackageDescription +import Distribution.Types.BuildInfo +import Distribution.Types.PackageDescription +import Distribution.Types.CondTree +import Distribution.Types.ForeignLib +import Distribution.Types.UnqualComponentName +import Distribution.Utils.Path +import Distribution.Compat.Lens +import Distribution.Types.BuildInfo.Lens qualified as L + +import Data.List (intercalate, nub) +import System.Process + + +main :: IO () +main = defaultMainWithHooks $ simpleUserHooks + { confHook = patchedConfHook } + + + +patchedConfHook + :: (GenericPackageDescription, HookedBuildInfo) + -> ConfigFlags + -> IO LocalBuildInfo +patchedConfHook (gpd, hbi) flags + | Just True <- lookupFlagAssignment ("python3-config") $ configConfigurationsFlags flags + = do cflags_raw <- readProcess "python3-config" ["--cflags"] "" + ldflags_raw <- readProcess "python3-config" ["--embed", "--ldflags"] "" + -- Split flags + let (inc_dirs,cflags) = partitionEithers + [ case flag of + '-':'I':path -> Left path + _ -> Right flag + | flag <- tokenizeArguments cflags_raw + ] + (ldflags, (libs, lib_dirs)) = fmap partitionEithers $ partitionEithers + [ case flag of + '-':'l':path -> Right (Left path) + '-':'L':path -> Right (Right path) + _ -> Left flag + | flag <- tokenizeArguments ldflags_raw + ] + let tweakLib lib = case lib.libName of + LMainLibName -> lib & L.ccOptions %~ (++ cflags) + & L.includeDirs %~ (++ (toSymb <$> inc_dirs)) + & L.ldOptions %~ (++ ldflags) + & L.extraLibs %~ (++ libs) + & L.extraLibDirs %~ (++ (toSymb <$> lib_dirs)) + _ -> lib + confHook simpleUserHooks + ( gpd { condLibrary = (fmap . fmap) tweakLib (condLibrary gpd) } + , hbi + ) flags + | otherwise = do + confHook simpleUserHooks (gpd, hbi) flags + +tokenizeArguments :: String -> [String] +tokenizeArguments = words + +---------------------------------------------------------------- +-- Compatibility + +#if MIN_VERSION_Cabal_syntax(3,14,0) +toSymb :: String -> SymbolicPathX 'AllowAbsolute from to +toSymb = makeSymbolicPath +#else +toSymb :: String -> String +toSymb = id +#endif diff --git a/inline-python.cabal b/inline-python.cabal index b98cad0..bd72c08 100644 --- a/inline-python.cabal +++ b/inline-python.cabal @@ -1,6 +1,6 @@ Cabal-Version: 3.0 -Build-Type: Simple - +Build-Type: Custom + Name: inline-python Version: 0.2.1.0 Synopsis: Python interpreter embedded into haskell. @@ -24,12 +24,24 @@ extra-source-files: py/bound-vars.py Tested-With: - GHC == 9.2.8 GHC == 9.4.8 GHC == 9.6.7 GHC == 9.8.4 GHC == 9.10.2 GHC == 9.12.2 + GHC == 9.14.1 + + +custom-setup + setup-depends: base >=4.15 && <5 + , Cabal >=3.0 && <4 + , Cabal-syntax >=3.6 && <4 + , process >=1.6 && <2 + +flag python3-config + Description: Use python3-config instead of pkg-config + Manual: True + Default: False source-repository head type: git @@ -55,7 +67,7 @@ common language ---------------------------------------------------------------- Library import: language - Build-Depends: base >=4.14 && <5 + Build-Depends: base >=4.15 && <5 , primitive >=0.6.2 , vector >=0.13.2 , containers >=0.5 @@ -72,7 +84,9 @@ Library include-dirs: include c-sources: cbits/python.c cc-options: -g -Wall - pkgconfig-depends: python3-embed >= 3.10 + -- Else we use python3-config in Setup.hs + if !flag(python3-config) + pkgconfig-depends: python3-embed >= 3.10 -- Exposed-modules: Python.Inline @@ -89,11 +103,10 @@ Library Python.Internal.Types Python.Internal.Util ----------------------------------------------------------------- -library test - import: language +common tests Default-Extensions: QuasiQuotes + hs-source-dirs: test build-depends: base , inline-python , tasty >=1.2 @@ -106,38 +119,35 @@ library test , vector , bytestring , text - hs-source-dirs: test - Exposed-modules: - TST.Run - TST.ToPy - TST.FromPy - TST.Callbacks - TST.Roundtrip - TST.Util + -- Running tests using several threads does very good job at finding threading -- bugs. Especially deadlocks test-suite inline-python-tests - import: language + import: language, tests type: exitcode-stdio-1.0 Ghc-options: -threaded -rtsopts -with-rtsopts=-N2 - hs-source-dirs: test/exe - main-is: main.hs - build-depends: base - , inline-python - , inline-python:test - , tasty + main-is: exe/main.hs + Other-modules: + TST.Run + TST.ToPy + TST.FromPy + TST.Callbacks + TST.Roundtrip + TST.Util test-suite inline-python-tests1 - import: language + import: language, tests type: exitcode-stdio-1.0 Ghc-options: -rtsopts - hs-source-dirs: test/exe - main-is: main.hs - build-depends: base - , inline-python - , inline-python:test - , tasty + main-is: exe/main.hs + Other-modules: + TST.Run + TST.ToPy + TST.FromPy + TST.Callbacks + TST.Roundtrip + TST.Util benchmark pysmall import: language diff --git a/src/Python/Inline.hs b/src/Python/Inline.hs index 82624bf..d387930 100644 --- a/src/Python/Inline.hs +++ b/src/Python/Inline.hs @@ -105,6 +105,7 @@ import Python.Internal.Eval -- -- Here's list of common problems and solutions and workarounds. -- +-- -- 1. __@inline-python@ cannot find libraries__ -- -- @inline-python@ may look for modules in wrong place. Set @@ -112,7 +113,23 @@ import Python.Internal.Eval -- right way. -- -- --- 2. __Linker error in GHCi__ +-- 2. __Picking correct python interpreter__ +-- +-- Python's version @inline-python@ uses is determined by @libpython3@ +-- it's linked with. This is decided when package is build. Normally +-- @pkg-config@ is used and this means using whatever distribution is +-- shipping. It's also possible to use @python3-config@ program by +-- specifying manual cabal flag @-fpython3-config@. +-- +-- If it's desired that @inline-python@ should use python installed by +-- conda\/uv\/etc @inline-python@ should be built in environment where +-- desired python version is active and use @-fpython3-config@ flag. +-- This could be done by adding following to cabal.project: +-- +-- > constraints: inline-python -fpython3-config +-- +-- +-- 3. __Linker error in GHCi__ -- -- Attempting to import library using C extensions from ghci may -- result in linker failing to find symbols from @libpython@ like diff --git a/src/Python/Internal/Eval.hs b/src/Python/Internal/Eval.hs index bbed590..7a24a64 100644 --- a/src/Python/Internal/Eval.hs +++ b/src/Python/Internal/Eval.hs @@ -518,7 +518,7 @@ runPyInMain py pure ( atomically (releaseLock tid_main) , evalInOtherThread tid_main eval_lock ) - -- If we513 can grab lock and main thread taken lock we're + -- If we can grab lock and main thread taken lock we're -- already executing on main thread. We can simply execute code Locked t ts | t /= tid