diff --git a/.github/workflows/ci-windows-macos.yml b/.github/workflows/ci-windows-macos.yml new file mode 100644 index 00000000..cc997d13 --- /dev/null +++ b/.github/workflows/ci-windows-macos.yml @@ -0,0 +1,44 @@ +# Windows + macOS lanes over the portable subset in cabal.project.ci +# (skips packages needing libtorch, system C libs, or foreign-library). + +name: CI (Windows + macOS) + +on: + push: + branches: [main] + pull_request: + +jobs: + build: + name: ${{ matrix.os }} / GHC ${{ matrix.ghc }} + strategy: + fail-fast: false + matrix: + os: [windows-latest, macos-14] + ghc: ["9.6.7", "9.12.2"] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + + - name: Set up GHC ${{ matrix.ghc }} + id: setup + uses: haskell-actions/setup@v2 + with: + ghc-version: ${{ matrix.ghc }} + cabal-version: latest + + - name: Freeze + run: cabal freeze --project-file=cabal.project.ci + + - name: Cache cabal store + uses: actions/cache@v4 + with: + path: ${{ steps.setup.outputs.cabal-store }} + key: ${{ runner.os }}-ghc-${{ matrix.ghc }}-${{ hashFiles('cabal.project.ci.freeze') }} + restore-keys: ${{ runner.os }}-ghc-${{ matrix.ghc }}- + + - name: Build (cabal.project.ci subset) + run: cabal build all --project-file=cabal.project.ci + + - name: Test (cabal.project.ci subset) + run: cabal test all --project-file=cabal.project.ci diff --git a/app/LazyBenchmark.hs b/app/LazyBenchmark.hs index 9cfd43f9..cd30a60d 100644 --- a/app/LazyBenchmark.hs +++ b/app/LazyBenchmark.hs @@ -8,7 +8,7 @@ Usage: cabal run lazy-bench [-- [OPTIONS]] (+RTS -s -RTS for heap stats) --rows N rows to generate (default 1_000_000_000) - --file PATH output CSV path (default /tmp/lazy_1b.csv) + --file PATH output CSV path (default: lazy_1b.csv in the system temp dir) --skip-gen reuse the file if it already exists -} module Main where @@ -22,7 +22,7 @@ import qualified DataFrame as D import qualified DataFrame.Lazy as L import DataFrame.Operators import DataFrame.Schema (Schema (..), schemaType) -import System.Directory (doesFileExist, getFileSize) +import System.Directory (doesFileExist, getFileSize, getTemporaryDirectory) import System.Environment (getArgs) import System.Exit (exitFailure) import System.IO ( @@ -42,8 +42,8 @@ import System.Random.Stateful defaultRows :: Int defaultRows = 1_000_000_000 -defaultFile :: FilePath -defaultFile = "/tmp/lazy_1b.csv" +defaultFile :: IO FilePath +defaultFile = (<> "/lazy_1b.csv") <$> getTemporaryDirectory -- Rows written per Builder flush to disk. chunkSize :: Int @@ -59,8 +59,8 @@ data Opts = Opts , optSkipGen :: Bool } -parseArgs :: [String] -> Either String Opts -parseArgs = go (Opts defaultRows defaultFile False) +parseArgs :: FilePath -> [String] -> Either String Opts +parseArgs defFile = go (Opts defaultRows defFile False) where go opts [] = Right opts go opts ("--rows" : n : rest) = case reads n of @@ -170,7 +170,8 @@ main :: IO () main = do hSetBuffering stdout LineBuffering args <- getArgs - opts <- case parseArgs args of + defFile <- defaultFile + opts <- case parseArgs defFile args of Left err -> putStrLn ("Error: " ++ err) >> exitFailure Right o -> return o diff --git a/dataframe-csv/src/DataFrame/IO/CSV.hs b/dataframe-csv/src/DataFrame/IO/CSV.hs index f9a7db4a..547f58d1 100644 --- a/dataframe-csv/src/DataFrame/IO/CSV.hs +++ b/dataframe-csv/src/DataFrame/IO/CSV.hs @@ -35,7 +35,7 @@ import qualified Data.ByteString.Lazy as BL import qualified Data.Map.Strict as M import qualified Data.Text as T import qualified Data.Text.Encoding as TE -import qualified Data.Text.IO as TIO +import qualified Data.Text.IO.Utf8 as TIO import Control.Exception (SomeException, catch) import Data.Maybe (fromMaybe) diff --git a/dataframe-lazy/src/DataFrame/Lazy/IO/CSV.hs b/dataframe-lazy/src/DataFrame/Lazy/IO/CSV.hs index c3a24c15..d4229302 100644 --- a/dataframe-lazy/src/DataFrame/Lazy/IO/CSV.hs +++ b/dataframe-lazy/src/DataFrame/Lazy/IO/CSV.hs @@ -103,6 +103,7 @@ readSeparated c opts path = do Nothing -> (0, totalRows') Just (start, len'') -> (start, min len'' (totalRows' - rowsRead opts)) withFile path ReadMode $ \handle -> do + hSetEncoding handle utf8 firstRow <- fmap T.strip . parseSep c <$> TIO.hGetLine handle let columnNames = if hasHeader opts @@ -256,6 +257,7 @@ openCsvStream :: openCsvStream sep schema path = do handle <- openFile path ReadMode hSetBuffering handle (BlockBuffering (Just (8 * 1024 * 1024))) + hSetEncoding handle utf8 headerLine <- TIO.hGetLine handle let headerCols = fmap (T.filter (/= '"') . T.strip) (parseSep sep headerLine) let schemaMap = elements schema diff --git a/dataframe-parsing/src-internal/DataFrame/Internal/Parsing.hs b/dataframe-parsing/src-internal/DataFrame/Internal/Parsing.hs index a7126247..7b922b39 100644 --- a/dataframe-parsing/src-internal/DataFrame/Internal/Parsing.hs +++ b/dataframe-parsing/src-internal/DataFrame/Internal/Parsing.hs @@ -17,7 +17,15 @@ import Data.Foldable (fold) import Data.Text.Read (decimal, double, signed) import Data.Time (Day, defaultTimeLocale, parseTimeM) import GHC.Stack (HasCallStack) -import System.IO (Handle, IOMode (..), hIsEOF, hTell, withFile) +import System.IO ( + Handle, + IOMode (..), + hIsEOF, + hSetEncoding, + hTell, + utf8, + withFile, + ) import Prelude hiding (takeWhile) isNullish :: T.Text -> Bool @@ -187,7 +195,8 @@ lineEnd = -- | First pass to count rows for exact allocation. countRows :: Char -> FilePath -> IO Int -countRows c path = withFile path ReadMode $! go 0 "" +countRows c path = withFile path ReadMode $ \h -> + hSetEncoding h utf8 >> go 0 "" h where go n input h = do isEOF <- hIsEOF h diff --git a/dataframe-viz/src/DataFrame/Display/Web/Plot.hs b/dataframe-viz/src/DataFrame/Display/Web/Plot.hs index 64dc8075..e28717b8 100644 --- a/dataframe-viz/src/DataFrame/Display/Web/Plot.hs +++ b/dataframe-viz/src/DataFrame/Display/Web/Plot.hs @@ -66,15 +66,17 @@ import Data.Char (chr) import qualified Data.List as L import qualified Data.Maybe import qualified Data.Text as T -import qualified Data.Text.IO as T +import qualified Data.Text.IO.Utf8 as T import GHC.Stack (HasCallStack) import Numeric (showFFloat) import System.Directory (getHomeDirectory) import System.Info (os) import System.Process ( + CreateProcess, StdStream (NoStream), createProcess, proc, + shell, std_err, std_in, std_out, @@ -458,6 +460,7 @@ plotBoxPlots ys = box (mkBox ys) -- Browser launcher -- --------------------------------------------------------------------------- +-- | Windows launches via a shell: 'start' is a cmd builtin. showInDefaultBrowser :: String -> IO () showInDefaultBrowser p = do plotId <- generateChartId @@ -471,15 +474,15 @@ showInDefaultBrowser p = do putStrLn fullPath T.writeFile fullPath (T.pack p) case os of - "mingw32" -> openFileSilently "start" fullPath - "darwin" -> openFileSilently "open" fullPath - _ -> openFileSilently "xdg-open" fullPath + "mingw32" -> launchSilently (shell ("start \"\" \"" <> fullPath <> "\"")) + "darwin" -> launchSilently (proc "open" [fullPath]) + _ -> launchSilently (proc "xdg-open" [fullPath]) -openFileSilently :: FilePath -> FilePath -> IO () -openFileSilently program path = do +launchSilently :: CreateProcess -> IO () +launchSilently cp = do (_, _, _, ph) <- createProcess - (proc program [path]) + cp { std_in = NoStream , std_out = NoStream , std_err = NoStream