From 8b1f1b117a03f780ce4ff2fdb76c2cfd2d1b49d4 Mon Sep 17 00:00:00 2001 From: Kristian Larsson Date: Tue, 2 Jun 2026 13:03:11 +0200 Subject: [PATCH 1/3] Build Acton compiler on Windows --- compiler/acton/Main.hs | 95 ++++++++++----------- compiler/acton/TerminalSize.hs | 13 ++- compiler/acton/ZigProgress.hs | 9 +- compiler/acton/package.yaml.in | 24 ++++-- compiler/lib/cbits/wcwidth.c | 52 +++++++++++ compiler/lib/package.yaml.in | 24 +++++- compiler/lib/src/Acton/CommandLineParser.hs | 4 + compiler/lib/src/Acton/Compile.hs | 40 +++++++-- compiler/lib/src/InterfaceFiles.hs | 35 ++++++-- compiler/stack.yaml | 4 + 10 files changed, 220 insertions(+), 80 deletions(-) create mode 100644 compiler/lib/cbits/wcwidth.c diff --git a/compiler/acton/Main.hs b/compiler/acton/Main.hs index 808fab0b0..14083218a 100644 --- a/compiler/acton/Main.hs +++ b/compiler/acton/Main.hs @@ -83,15 +83,15 @@ import System.Environment (lookupEnv) import System.Exit import System.FileLock import System.FilePath ((), addTrailingPathSeparator) -import System.FilePath.Posix +import System.FilePath.Posix hiding ((), addTrailingPathSeparator) import System.IO hiding (readFile, writeFile) import Text.PrettyPrint (renderStyle, style, Style(..), Mode(PageMode)) -import Text.Show.Pretty (ppDoc) import System.IO.Temp import System.IO.Unsafe (unsafePerformIO) import System.Info -import System.Posix.Files -import System.Posix.IO (createPipe, setFdOption, closeFd, FdOption(..)) +#if !defined(mingw32_HOST_OS) +import System.Posix.IO (createPipe, fdToHandle, setFdOption, closeFd, FdOption(..)) +#endif import System.Process hiding (createPipe) import qualified System.FSNotify as FS import qualified System.Environment @@ -836,24 +836,14 @@ dispatchWatchTrigger notify trigger = -- | Classify project FS events into full or incremental rebuild triggers. actWatchTrigger :: FS.Event -> Maybe WatchTrigger actWatchTrigger ev = - case FS.eventIsDirectory ev of - FS.IsDirectory -> - case ev of - FS.Added{} -> Just WatchFull - FS.Removed{} -> Just WatchFull - FS.WatchedDirectoryRemoved{} -> Just WatchFull - FS.Unknown{} -> Just WatchFull - _ -> Nothing - FS.IsFile -> - if takeExtension (FS.eventPath ev) /= ".act" - then Nothing - else case ev of - FS.ModifiedAttributes{} -> Nothing - FS.Added{} -> Just WatchFull - FS.Removed{} -> Just WatchFull - FS.WatchedDirectoryRemoved{} -> Just WatchFull - FS.Unknown{} -> Just WatchFull - _ -> Just (WatchIncremental (FS.eventPath ev)) + let path = FS.eventPath ev + in if takeExtension path /= ".act" + then Nothing + else case ev of + FS.Added{} -> Just WatchFull + FS.Removed{} -> Just WatchFull + FS.Unknown{} -> Just WatchFull + _ -> Just (WatchIncremental path) -- | Classify FS events for a specific file path. fileWatchTrigger :: FilePath -> FS.Event -> Maybe WatchTrigger @@ -862,7 +852,8 @@ fileWatchTrigger target ev = in if evPath /= target then Nothing else case ev of - FS.ModifiedAttributes{} -> Nothing + FS.Removed{} -> Just WatchFull + FS.Unknown{} -> Just WatchFull _ -> Just (WatchIncremental (FS.eventPath ev)) -- | Run the project watch loop and schedule compiles on events. @@ -2340,32 +2331,38 @@ runZig gopts opts zigExe zigArgs paths wd mProgressUI = do ui <- mProgressUI let tp = puTermProgress ui if termProgressEnabled tp then Just tp else Nothing - (envOverride, onStart, onStop, closeFds) <- case mTermProgress of - Nothing -> return (Nothing, \_ -> return (), return (), True) - Just tp -> do - (readFd, writeFd) <- createPipe - setFdOption writeFd CloseOnExec False - setFdOption readFd CloseOnExec True - doneVar <- newEmptyMVar - pctRef <- newIORef (85 :: Int) - let updatePercent ratio = do - let clamped = max 0 (min 0.999 ratio) - pctRaw = 85 + floor (clamped * 15) - pct <- atomicModifyIORef' pctRef (\prev -> let next = max prev pctRaw in (next, next)) - withLock (termProgressPercent tp pct) - reader = do - readZigProgressStream readFd $ \msg -> - case zigProgressRatio msg of - Nothing -> return () - Just ratio -> updatePercent ratio - _ <- forkIO (reader `finally` putMVar doneVar ()) - let envVal = show (fromIntegral writeFd :: Int) - onStart' _ = closeFd writeFd `catch` ignoreIO - onStop' = do - closeFd writeFd `catch` ignoreIO - closeFd readFd `catch` ignoreIO - takeMVar doneVar - return (Just ("ZIG_PROGRESS", envVal), onStart', onStop', False) + (envOverride, onStart, onStop, closeFds) <- +#if defined(mingw32_HOST_OS) + return (Nothing, \_ -> return (), return (), True) +#else + case mTermProgress of + Nothing -> return (Nothing, \_ -> return (), return (), True) + Just tp -> do + (readFd, writeFd) <- createPipe + setFdOption writeFd CloseOnExec False + setFdOption readFd CloseOnExec True + readHandle <- fdToHandle readFd + doneVar <- newEmptyMVar + pctRef <- newIORef (85 :: Int) + let updatePercent ratio = do + let clamped = max 0 (min 0.999 ratio) + pctRaw = 85 + floor (clamped * 15) + pct <- atomicModifyIORef' pctRef (\prev -> let next = max prev pctRaw in (next, next)) + withLock (termProgressPercent tp pct) + reader = do + readZigProgressStream readHandle $ \msg -> + case zigProgressRatio msg of + Nothing -> return () + Just ratio -> updatePercent ratio + _ <- forkIO (reader `finally` putMVar doneVar ()) + let envVal = show (fromIntegral writeFd :: Int) + onStart' _ = closeFd writeFd `catch` ignoreIO + onStop' = do + closeFd writeFd `catch` ignoreIO + hClose readHandle `catch` ignoreIO + takeMVar doneVar + return (Just ("ZIG_PROGRESS", envVal), onStart', onStop', False) +#endif let env1 = if System.Info.os == "darwin" && not (any ((== "DEVELOPER_DIR") . fst) env0) then ("DEVELOPER_DIR", "/dev/null") : env0 else env0 diff --git a/compiler/acton/TerminalSize.hs b/compiler/acton/TerminalSize.hs index 21b96b240..98fc44afc 100644 --- a/compiler/acton/TerminalSize.hs +++ b/compiler/acton/TerminalSize.hs @@ -1,4 +1,5 @@ {-# LANGUAGE CApiFFI #-} +{-# LANGUAGE CPP #-} module TerminalSize ( TermSize @@ -18,11 +19,13 @@ import Control.Exception (SomeException, try) import Control.Monad (when) import Data.IORef import Data.List (isInfixOf) +#if !defined(mingw32_HOST_OS) import Foreign import Foreign.C.Types -import System.Environment (lookupEnv) import System.Posix.IO (stdOutput) import System.Posix.Signals (Handler(..), installHandler) +#endif +import System.Environment (lookupEnv) data TermSize = TermSize { tsEnabled :: Bool @@ -31,6 +34,7 @@ data TermSize = TermSize , tsDirtyRef :: IORef Bool } +#if !defined(mingw32_HOST_OS) data WinSize = WinSize { wsRows :: CUShort , wsCols :: CUShort @@ -61,6 +65,7 @@ foreign import capi unsafe "signal.h value SIGWINCH" foreign import capi unsafe "sys/ioctl.h ioctl" c_ioctlWinsize :: CInt -> CULong -> Ptr WinSize -> IO CInt +#endif defaultRows :: Int defaultRows = 24 @@ -82,8 +87,10 @@ initTermSize enabled = do , tsDirtyRef = dirtyRef } when enabled $ do +#if !defined(mingw32_HOST_OS) _ <- try (installHandler (fromIntegral c_SIGWINCH) (Catch (writeIORef dirtyRef True)) Nothing) :: IO (Either SomeException Handler) +#endif _ <- termSizeSync ts return () return ts @@ -168,6 +175,9 @@ termRenderedRowsTotal :: Int -> [String] -> Int termRenderedRowsTotal width = sum . map (termRenderedRows width) queryTermSize :: IO (Maybe (Int, Int)) +#if defined(mingw32_HOST_OS) +queryTermSize = return Nothing +#else queryTermSize = alloca $ \ptr -> do rc <- c_ioctlWinsize (fromIntegral stdOutput) c_TIOCGWINSZ ptr @@ -180,6 +190,7 @@ queryTermSize = if rows' > 0 && cols' > 0 then return (Just (rows', cols')) else return Nothing +#endif envOrDefault :: String -> Int -> IO Int envOrDefault name fallback = do diff --git a/compiler/acton/ZigProgress.hs b/compiler/acton/ZigProgress.hs index 903cf04f2..8e3bc478b 100644 --- a/compiler/acton/ZigProgress.hs +++ b/compiler/acton/ZigProgress.hs @@ -13,8 +13,7 @@ import qualified Data.ByteString.Char8 as BSC import Data.List (foldl') import qualified Data.Set as Set import Data.Word (Word8, Word32) -import qualified System.Posix.IO.ByteString as PIOB -import System.Posix.Types (Fd) +import System.IO (Handle) data ZigNode = ZigNode { znCompleted :: Word32 @@ -27,11 +26,11 @@ data ZigProgress = ZigProgress { zpNodes :: [ZigNode] } deriving (Show) -readZigProgressStream :: Fd -> (ZigProgress -> IO ()) -> IO () -readZigProgressStream fd onMsg = go BS.empty +readZigProgressStream :: Handle -> (ZigProgress -> IO ()) -> IO () +readZigProgressStream handle onMsg = go BS.empty where go buf = do - res <- (try (PIOB.fdRead fd 4096) :: IO (Either IOException BS.ByteString)) + res <- (try (BS.hGetSome handle 4096) :: IO (Either IOException BS.ByteString)) case res of Left _ -> return () Right chunk | BS.null chunk -> return () diff --git a/compiler/acton/package.yaml.in b/compiler/acton/package.yaml.in index 42faa9f1f..271656edd 100644 --- a/compiler/acton/package.yaml.in +++ b/compiler/acton/package.yaml.in @@ -41,23 +41,21 @@ dependencies: - optparse-applicative - megaparsec - prettyprinter - - pretty-show - process - random - split - system-filepath - - sydtest - - tasty - - tasty-expected-failure - - tasty-golden - - tasty-hunit - text - temporary - time - timeit - - unix - unordered-containers - regex-tdfa + +when: + - condition: "!os(windows)" + dependencies: + - unix executables: acton: @@ -97,6 +95,11 @@ tests: test_acton: main: test.hs source-dirs: . + dependencies: + - tasty + - tasty-expected-failure + - tasty-golden + - tasty-hunit ghc-options: - -threaded - -rtsopts @@ -104,6 +107,10 @@ tests: incremental: main: test_incremental.hs source-dirs: . + dependencies: + - tasty + - tasty-golden + - tasty-hunit ghc-options: - -threaded - -rtsopts @@ -111,6 +118,9 @@ tests: test_acton_online: main: test_online.hs source-dirs: . + dependencies: + - tasty + - tasty-hunit ghc-options: - -threaded - -rtsopts diff --git a/compiler/lib/cbits/wcwidth.c b/compiler/lib/cbits/wcwidth.c new file mode 100644 index 000000000..8b3eec615 --- /dev/null +++ b/compiler/lib/cbits/wcwidth.c @@ -0,0 +1,52 @@ +#if defined(_WIN32) +#include + +static int in_range(unsigned int c, unsigned int lo, unsigned int hi) { + return c >= lo && c <= hi; +} + +int wcwidth(wchar_t wc) { + unsigned int c = (unsigned int)wc; + + if (c == 0) { + return 0; + } + if (c < 32 || in_range(c, 0x7f, 0x9f)) { + return -1; + } + if (in_range(c, 0x0300, 0x036f) || + in_range(c, 0x0483, 0x0489) || + in_range(c, 0x0591, 0x05bd) || + c == 0x05bf || + in_range(c, 0x05c1, 0x05c2) || + in_range(c, 0x05c4, 0x05c5) || + c == 0x05c7 || + in_range(c, 0x0610, 0x061a) || + in_range(c, 0x064b, 0x065f) || + c == 0x0670 || + in_range(c, 0x06d6, 0x06dc) || + in_range(c, 0x06df, 0x06e4) || + in_range(c, 0x06e7, 0x06e8) || + in_range(c, 0x06ea, 0x06ed) || + in_range(c, 0x200b, 0x200f) || + in_range(c, 0x202a, 0x202e) || + in_range(c, 0x2060, 0x206f) || + in_range(c, 0xfe00, 0xfe0f)) { + return 0; + } + if (in_range(c, 0x1100, 0x115f) || + c == 0x2329 || + c == 0x232a || + in_range(c, 0x2e80, 0xa4cf) || + in_range(c, 0xac00, 0xd7a3) || + in_range(c, 0xf900, 0xfaff) || + in_range(c, 0xfe10, 0xfe19) || + in_range(c, 0xfe30, 0xfe6f) || + in_range(c, 0xff00, 0xff60) || + in_range(c, 0xffe0, 0xffe6)) { + return 2; + } + + return 1; +} +#endif diff --git a/compiler/lib/package.yaml.in b/compiler/lib/package.yaml.in index ac9d8844f..593fa821f 100644 --- a/compiler/lib/package.yaml.in +++ b/compiler/lib/package.yaml.in @@ -43,24 +43,29 @@ dependencies: - optparse-applicative - parser-combinators - pretty - - pretty-show - prettyprinter - process - random - scientific - stm - - sydtest - - sydtest-discover - temporary - text - time - transformers - - unix - unordered-containers - utf8-string +when: + - condition: "!os(windows)" + dependencies: + - unix + library: source-dirs: src + when: + - condition: os(windows) + c-sources: + - cbits/wcwidth.c exposed-modules: - Acton.Boxing - Acton.BuildSpec @@ -100,6 +105,8 @@ tests: source-dirs: test dependencies: - libacton + - sydtest + - sydtest-discover ghc-options: - -threaded - -rtsopts @@ -112,6 +119,9 @@ executables: other-modules: [] dependencies: - libacton + when: + - condition: os(windows) + buildable: false ghc-options: - -threaded - -rtsopts @@ -122,6 +132,9 @@ executables: other-modules: [] dependencies: - libacton + when: + - condition: os(windows) + buildable: false ghc-options: - -threaded - -rtsopts @@ -132,6 +145,9 @@ executables: other-modules: [] dependencies: - libacton + when: + - condition: os(windows) + buildable: false ghc-options: - -threaded - -rtsopts diff --git a/compiler/lib/src/Acton/CommandLineParser.hs b/compiler/lib/src/Acton/CommandLineParser.hs index e023450ef..c3fd5a297 100644 --- a/compiler/lib/src/Acton/CommandLineParser.hs +++ b/compiler/lib/src/Acton/CommandLineParser.hs @@ -13,6 +13,10 @@ defTarget = "x86_64-macos-none" defTarget = "aarch64-linux-gnu.2.27" #elif defined(linux_HOST_OS) && defined(x86_64_HOST_ARCH) defTarget = "x86_64-linux-gnu.2.27" +#elif defined(mingw32_HOST_OS) && defined(aarch64_HOST_ARCH) +defTarget = "aarch64-linux-gnu.2.27" +#elif defined(mingw32_HOST_OS) && defined(x86_64_HOST_ARCH) +defTarget = "x86_64-linux-gnu.2.27" #else #error "Unsupported platform" #endif diff --git a/compiler/lib/src/Acton/Compile.hs b/compiler/lib/src/Acton/Compile.hs index 8dd10287d..823c898ae 100644 --- a/compiler/lib/src/Acton/Compile.hs +++ b/compiler/lib/src/Acton/Compile.hs @@ -240,6 +240,7 @@ import qualified Data.Map as M import Data.Ord (Down(..)) import qualified Data.Set import Data.Time.Clock (UTCTime) +import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds) import Data.Word (Word8, Word32, Word64) import Error.Diagnose (Diagnostic) import GHC.Conc (getNumCapabilities) @@ -253,16 +254,17 @@ import System.Directory.Recursive (getFilesRecursive) import System.Environment (getExecutablePath, lookupEnv) import System.FileLock (FileLock, SharedExclusive(Exclusive), tryLockFile, unlockFile, withFileLock) import System.FilePath (()) -import System.FilePath.Posix +import System.FilePath.Posix hiding (()) import System.Exit (ExitCode(..)) import System.IO hiding (readFile, writeFile) import System.IO.Temp (createTempDirectory, withSystemTempDirectory) import System.IO.Unsafe (unsafePerformIO) +#if !defined(mingw32_HOST_OS) import System.Posix.Files (FileStatus, deviceID, fileID, fileSize, getFileStatus, modificationTimeHiRes, statusChangeTimeHiRes) +#endif import System.Process (CreateProcess(cwd), readCreateProcessWithExitCode, proc) import System.Random (randomRIO) -import Text.PrettyPrint (renderStyle, style, Style(..), Mode(PageMode)) -import Text.Show.Pretty (ppDoc) +import Text.PrettyPrint (renderStyle, style, Style(..), Mode(PageMode), text) import Text.Printf import qualified Data.ByteString as BS @@ -1041,6 +1043,17 @@ parseActFile opts sp mn actFile mOnProgress = do -- restores falls back to content hashing instead of blindly trusting mtimes. readSourceFileMeta :: FilePath -> IO InterfaceFiles.SourceFileMeta readSourceFileMeta path = do +#if defined(mingw32_HOST_OS) + mtimeNs <- fileModificationTimeNs path + size <- getFileSize path + return InterfaceFiles.SourceFileMeta + { InterfaceFiles.sfmMTimeNs = mtimeNs + , InterfaceFiles.sfmCTimeNs = mtimeNs + , InterfaceFiles.sfmSize = fromIntegral size + , InterfaceFiles.sfmDevice = Nothing + , InterfaceFiles.sfmInode = Nothing + } +#else st <- getFileStatus path let mtimeNs = fileStatusMTimeNs st ctimeNs = fileStatusCTimeNs st @@ -1054,6 +1067,19 @@ readSourceFileMeta path = do where fileStatusMTimeNs st = floor (toRational (modificationTimeHiRes st) * 1000000000) fileStatusCTimeNs st = floor (toRational (statusChangeTimeHiRes st) * 1000000000) +#endif + +fileModificationTimeNs :: FilePath -> IO Integer +#if defined(mingw32_HOST_OS) +fileModificationTimeNs path = + utcTimeToNs <$> getModificationTime path + where + utcTimeToNs = floor . (* (1000000000 :: Rational)) . toRational . utcTimeToPOSIXSeconds +#else +fileModificationTimeNs path = do + st <- getFileStatus path + return (floor (toRational (modificationTimeHiRes st) * 1000000000)) +#endif -- Compilation tasks, chasing imported modules, compilation and building executables ----------------- @@ -1415,8 +1441,6 @@ readModuleHeader sp gopts opts paths actFile = do snap <- Source.spReadFile sp actFile verifyOrParse mn snap moduleSrcBytesHash modulePubHash moduleImplHash cachedSourceMeta (Just currentSourceMeta) imps nameHashes roots tests mdoc where - fileStatusMTimeNs st = floor (toRational (modificationTimeHiRes st) * 1000000000) - mkTyHead mn moduleSrcBytesHash modulePubHash moduleImplHash imps nameHashes roots tests mdoc = TyHead { mhName = mn @@ -1462,10 +1486,10 @@ readModuleHeader sp gopts opts paths actFile = do case exePathE of Left _ -> return False Right exePath -> do - exeStatusE <- (try (getFileStatus exePath) :: IO (Either SomeException FileStatus)) + exeStatusE <- (try (fileModificationTimeNs exePath) :: IO (Either SomeException Integer)) case exeStatusE of Left _ -> return False - Right exeStatus -> return (fileStatusMTimeNs exeStatus > tyMTimeNs) + Right exeMTimeNs -> return (exeMTimeNs > tyMTimeNs) refreshCachedSourceMeta currentSourceMeta = do let tyFilePath = tyDbPath paths (modName paths) @@ -1833,7 +1857,7 @@ runFrontPasses gopts opts paths env0 parsed srcContent srcBytes sourceMeta resol when (C.parse opts && isRoot) $ dump mn "parse" (Pretty.print parsed) when (C.parse_ast opts && isRoot) $ - dump mn "parse-ast" (renderStyle prettyAstStyle (ppDoc parsed)) + dump mn "parse-ast" (renderStyle prettyAstStyle (text (show parsed))) typeStmtTimingsRef <- newIORef ([] :: [TypeStmtTiming]) inferredSigsRef <- newIORef ([] :: [InferredSignature]) diff --git a/compiler/lib/src/InterfaceFiles.hs b/compiler/lib/src/InterfaceFiles.hs index 506977520..72a4db190 100644 --- a/compiler/lib/src/InterfaceFiles.hs +++ b/compiler/lib/src/InterfaceFiles.hs @@ -11,7 +11,7 @@ -- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -- -{-# LANGUAGE DeriveGeneric, ScopedTypeVariables #-} +{-# LANGUAGE CPP, DeriveGeneric, ScopedTypeVariables #-} -- Acton Interface (.tydb) Files -- -- Purpose @@ -102,17 +102,20 @@ import qualified Data.ByteString.Lazy as BL import qualified Data.Text as T import qualified Data.Text.Encoding as TE import Data.Time.Clock (UTCTime) +import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds) import qualified Database.LMDB.Raw as LMDB import qualified Acton.Syntax as A import qualified Acton.NameInfo as I import Foreign.Ptr (castPtr) import Foreign.Storable (peek) import GHC.Generics (Generic) -import System.Directory (canonicalizePath, createDirectoryIfMissing, doesDirectoryExist, doesFileExist, getFileSize, getModificationTime, listDirectory, removeFile, removePathForcibly) +import System.Directory (canonicalizePath, createDirectoryIfMissing, doesDirectoryExist, doesFileExist, getFileSize, getModificationTime, getPermissions, listDirectory, removeFile, removePathForcibly, writable) import System.FilePath ((), takeExtension) import System.IO.Error (isDoesNotExistError) import System.IO.Unsafe (unsafePerformIO) +#if !defined(mingw32_HOST_OS) import System.Posix.Files (fileAccess, getFileStatus, modificationTimeHiRes, setFileMode) +#endif data NameHashInfo = NameHashInfo { nhName :: A.Name @@ -203,8 +206,13 @@ interfaceModifiedTime path = getModificationTime (dataFilePath path) interfaceModifiedTimeNs :: FilePath -> IO Integer interfaceModifiedTimeNs path = do +#if defined(mingw32_HOST_OS) + time <- getModificationTime (dataFilePath path) + return (utcTimeToNs time) +#else st <- getFileStatus (dataFilePath path) return (floor (toRational (modificationTimeHiRes st) * 1000000000)) +#endif copyInterface :: FilePath -> FilePath -> IO () copyInterface src dst = do @@ -434,8 +442,8 @@ canUseLockFile path = do let lockPath = lockFilePath path exists <- doesFileExist lockPath if exists - then fileAccess lockPath False True False - else fileAccess path False True True + then canWritePath lockPath False + else canWritePath path True canReadWithoutLock :: FilePath -> IO Bool canReadWithoutLock path = do @@ -444,10 +452,18 @@ canReadWithoutLock path = do if not exists then return False else do - dataWritable <- fileAccess dataPath False True False - dirWritable <- fileAccess path False True True + dataWritable <- canWritePath dataPath False + dirWritable <- canWritePath path True return (not dataWritable && not dirWritable) +canWritePath :: FilePath -> Bool -> IO Bool +#if defined(mingw32_HOST_OS) +canWritePath path _ = + (writable <$> getPermissions path) `E.catch` \(_ :: E.IOException) -> return False +#else +canWritePath path searchable = fileAccess path False True searchable +#endif + isCorruptEnv :: E.SomeException -> Bool isCorruptEnv err = case E.fromException err of @@ -483,6 +499,9 @@ writeEntries path entries = do Left (err :: E.SomeException) -> E.throwIO err setReadableInterfacePermissions :: FilePath -> IO () +#if defined(mingw32_HOST_OS) +setReadableInterfacePermissions _ = return () +#else setReadableInterfacePermissions path = do setFileMode path 0o755 setFileMode (dataFilePath path) 0o644 @@ -494,6 +513,10 @@ setReadableInterfacePermissions path = do if isDoesNotExistError err then return () else E.throwIO (err :: E.IOException) +#endif + +utcTimeToNs :: UTCTime -> Integer +utcTimeToNs = floor . (* (1000000000 :: Rational)) . toRational . utcTimeToPOSIXSeconds validateVersion :: LMDB.MDB_txn -> LMDB.MDB_dbi -> IO () validateVersion txn dbi = do diff --git a/compiler/stack.yaml b/compiler/stack.yaml index 6532b7d4a..6a856ac03 100644 --- a/compiler/stack.yaml +++ b/compiler/stack.yaml @@ -43,12 +43,15 @@ extra-deps: - dir-traverse-0.2.3.0 - directory-1.3.8.5 - filepath-1.4.300.1 + - fsnotify-0.3.0.1@sha256:58bb530d7acf93eb4ed69473e32a1485581815f04f69dfc8a278523781ba49dd,2988 - lsp-2.7.0.0 - lsp-types-2.3.0.0 - parsec-3.1.17.0@sha256:8407cbd428d7f640a0fff8891bd2f7aca13cebe70a5e654856f8abec9a648b56 - process-1.6.19.0 - tasty-1.5.3 + - time-1.12.2 - unix-2.8.2.1 + - Win32-2.14.2.2 # Override default flag values for local packages and extra-deps # flags: {} @@ -69,3 +72,4 @@ extra-lib-dirs: ghc-options: "$everything": -fPIC + lsp-types: -O0 -fexternal-interpreter From 9d4e3bbc106c49b9e6db29af3ec5bc1ff66e8775 Mon Sep 17 00:00:00 2001 From: Kristian Larsson Date: Tue, 2 Jun 2026 19:07:35 +0200 Subject: [PATCH 2/3] Support building Acton distribution on Windows --- Makefile | 189 ++++++++++++++------ compiler/acton/Main.hs | 90 ++++++---- compiler/acton/PkgCommands.hs | 10 +- compiler/acton/package.yaml.in | 3 + compiler/lib/package.yaml.in | 2 + compiler/lib/src/Acton/CommandLineParser.hs | 11 +- compiler/lib/src/Acton/Compile.hs | 57 ++++-- compiler/lib/src/InterfaceFiles.hs | 19 +- compiler/lib/src/Utils.hs | 14 +- compiler/stack.yaml | 4 +- compiler/stack.yaml.lock | 23 ++- deps/libargp/build.zig.zon | 8 + deps/libbsdnt/build.zig.zon | 8 + deps/libgc/build.zig.zon | 8 + deps/libnetstring/build.zig.zon | 8 + deps/libprotobuf_c/build.zig.zon | 8 + deps/libsnappy_c/build.zig.zon | 8 + deps/libutf8proc/build.zig.zon | 8 + deps/libuuid/build.zig.zon | 8 + deps/libuv/build.zig.zon | 8 + deps/libxml2/build.zig.zon | 8 + deps/libyyjson/build.zig.zon | 8 + deps/mbedtls/build.zig.zon | 8 + deps/pcre2/build.zig.zon | 8 + 24 files changed, 412 insertions(+), 114 deletions(-) create mode 100644 deps/libargp/build.zig.zon create mode 100644 deps/libbsdnt/build.zig.zon create mode 100644 deps/libgc/build.zig.zon create mode 100644 deps/libnetstring/build.zig.zon create mode 100644 deps/libprotobuf_c/build.zig.zon create mode 100644 deps/libsnappy_c/build.zig.zon create mode 100644 deps/libutf8proc/build.zig.zon create mode 100644 deps/libuuid/build.zig.zon create mode 100644 deps/libuv/build.zig.zon create mode 100644 deps/libxml2/build.zig.zon create mode 100644 deps/libyyjson/build.zig.zon create mode 100644 deps/mbedtls/build.zig.zon create mode 100644 deps/pcre2/build.zig.zon diff --git a/Makefile b/Makefile index 9d1bfd2cf..18d55f4b6 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,25 @@ include version.mk TD := $(CURDIR) +UNAME_S := $(shell uname -s) +UNAME_M := $(shell uname -m) CHANGELOG_VERSION=$(shell grep '^\#\# \[[0-9]' CHANGELOG.md | sed 's/\#\# \[\([^]]\{1,\}\)].*/\1/' | head -n1) GIT_VERSION_TAG=$(shell git tag --points-at HEAD 2>/dev/null | grep "v[0-9]" | sed -e 's/^v//') +ifeq ($(OS),Windows_NT) +OS:=windows +else ifneq ($(findstring MINGW,$(UNAME_S)),) +OS:=windows +else ifneq ($(findstring MSYS_NT,$(UNAME_S)),) +OS:=windows +endif + +EXE_SUFFIX= +ifeq ($(OS),windows) +EXE_SUFFIX=.exe +export GHC_CHARENC=UTF-8 +export LANG=C.UTF-8 +endif + ifdef HOME ZIG_LOCAL_CACHE_DIR ?= $(HOME)/.cache/acton/zig-local-cache ZIG_GLOBAL_CACHE_DIR ?= $(HOME)/.cache/acton/zig-global-cache @@ -14,10 +31,13 @@ endif export ZIG_LOCAL_CACHE_DIR export ZIG_GLOBAL_CACHE_DIR -ACTON=$(TD)/dist/bin/acton -ACTONC=dist/bin/actonc +ACTON_BIN=dist/bin/acton$(EXE_SUFFIX) +ACTONC=dist/bin/actonc$(EXE_SUFFIX) +LSP_SERVER=dist/bin/lsp-server-acton$(EXE_SUFFIX) +ACTONDB=dist/bin/actondb$(EXE_SUFFIX) +ACTON=$(TD)/$(ACTON_BIN) ZIG_VERSION:=0.16.0 -ZIG=$(TD)/dist/zig/zig +ZIG=$(TD)/dist/zig/zig$(EXE_SUFFIX) CURL:=curl --fail --location --retry 5 --retry-delay 2 --retry-max-time 120 --retry-all-errors --retry-connrefused AR=$(ZIG) ar CC=$(ZIG) cc @@ -30,6 +50,10 @@ ACTON_STACK_NEEDS_ZIG= ACTON_ZIG_TARGET= STACK=unset CC && unset CXX && unset CFLAGS && unset CPPFLAGS && unset LDFLAGS && unset ACTON_REAL_LD && stack +ifeq ($(OS),windows) +STACK=chcp.com 65001 >/dev/null && unset CC && unset CXX && unset CFLAGS && unset CPPFLAGS && unset LDFLAGS && unset ACTON_REAL_LD && stack +endif + # Determine which xargs we have. BSD xargs does not have --no-run-if-empty, # rather, it is the default behavior so the argument is superfluous. We check if # we are using GNU xargs by trying to run xargs --version and grep for 'GNU', if @@ -66,30 +90,30 @@ ACTC_GHC_OPTS += -fprof-auto -fprof-cafs endif # rewrite arm64 to aarch64 -ifeq ($(shell uname -m),arm64) +ifeq ($(UNAME_M),arm64) ARCH:=aarch64 else -ARCH:=$(shell uname -m) +ARCH:=$(UNAME_M) endif # -- Apple Mac OS X ------------------------------------------------------------ -ifeq ($(shell uname -s),Darwin) +ifeq ($(UNAME_S),Darwin) OS:=macos endif # -- Linux --------------------------------------------------------------------- -ifeq ($(shell uname -s),Linux) +ifeq ($(UNAME_S),Linux) OS:=linux ACTON_ZIG_GLIBC_VERSION ?= 2.31 export ACTON_ZIG_GLIBC_VERSION ACTON_STACK_NEEDS_ZIG=1 STACK=CC="$(ACTON_STACK_CC)" CXX="$(ACTON_STACK_CXX)" CFLAGS= CPPFLAGS= LDFLAGS= ACTON_REAL_LD="$(ACTON_STACK_CC)" stack --with-gcc="$(ACTON_STACK_CC)" -ifeq ($(shell uname -m),x86_64) +ifeq ($(UNAME_M),x86_64) ACTON_ZIG_TARGET := x86_64-linux-gnu.$(ACTON_ZIG_GLIBC_VERSION) -else ifeq ($(shell uname -m),aarch64) +else ifeq ($(UNAME_M),aarch64) ACTON_ZIG_TARGET := aarch64-linux-gnu.$(ACTON_ZIG_GLIBC_VERSION) else -$(error "Unsupported architecture for Linux?" $(shell uname -m)) +$(error "Unsupported architecture for Linux?" $(UNAME_M)) endif ACTONC_TARGET := --target $(ACTON_ZIG_TARGET) endif # -- END: Linux ---------------------------------------------------------- @@ -104,7 +128,7 @@ help: @echo "Available make targets:" @echo " all - build everything" @echo " test - run the test suite" - @echo " make PROFILE=1 dist/bin/acton - build profiled acton binary" + @echo " make PROFILE=1 $(ACTON_BIN) - build profiled acton binary" @echo "" @echo " clean - /normal/ clean repo" @echo " clean-all - thorough cleaning" @@ -124,7 +148,13 @@ endif BUILTIN_HFILES=$(wildcard base/builtin/*.h) -DIST_BINS=$(ACTONC) dist/bin/actondb dist/bin/runacton dist/bin/lsp-server-acton +DIST_BINS=$(ACTONC) dist/bin/runacton $(LSP_SERVER) +ifneq ($(OS),windows) +DIST_BINS += $(ACTONDB) +endif +ifeq ($(OS),windows) +DIST_BINS += dist/bin/liblmdb.dll +endif DIST_ZIG=dist/zig ifeq ($(ACTON_STACK_NEEDS_ZIG),1) ACTON_STACK_PREREQS=$(DIST_ZIG) @@ -142,14 +172,13 @@ endif # and needs a fixed location independent of the directory the link runs from. ACTON_BDEPS_DIR := $(TD)/bdeps/out export ACTON_BDEPS_DIR -# libz is the one bdeps lib that GHC actually links into acton on every platform, -# so we build and statically link it everywhere. The ld-wrapper rewrites -lz to -# this archive's full path; dropping any future lib's .a into bdeps/out/lib (e.g. -# liblmdb.a) makes it link statically the same way, with no per-lib wiring. +ifneq ($(OS),windows) +# libz and liblmdb are linked statically into acton on Unix-like platforms. On +# Windows GHC links against the Stack/MSYS2 LMDB DLL, which we ship app-locally +# beside acton.exe instead. BDEPS += bdeps/out/lib/libz.a -# liblmdb backs the compiler's .tydb interface cache; GHC links it into acton on -# every platform, so -- unlike gmp/tinfo below -- it is an unconditional bdep. BDEPS += bdeps/out/lib/liblmdb.a +endif ifeq ($(OS),linux) # gmp and tinfo are only pulled in by GHC on Linux (macOS GHC uses the native # bignum backend and the SDK's libncurses), so they are Linux-only bdeps. @@ -169,34 +198,53 @@ test-backend: $(BACKEND_TESTS) # /compiler ---------------------------------------------- ACTONC_HS=$(wildcard compiler/lib/src/*.hs compiler/lib/src/*/*.hs compiler/acton/*.hs compiler/acton/*/*.hs) ACTONLSP_HS=$(wildcard compiler/lsp-server/*.hs) -dist/bin/acton: compiler/lib/package.yaml.in compiler/acton/package.yaml.in compiler/lsp-server/package.yaml.in compiler/stack.yaml $(ACTONC_HS) $(ACTONLSP_HS) version.mk dist/builder $(ACTON_STACK_PREREQS) $(BDEPS) + +ifeq ($(OS),windows) +ACTONC_LINK_CMD=cp -f dist/bin/acton.exe dist/bin/actonc.exe +else +ACTONC_LINK_CMD=ln -sf acton dist/bin/actonc +endif + +$(ACTON_BIN): compiler/lib/package.yaml.in compiler/acton/package.yaml.in compiler/lsp-server/package.yaml.in compiler/stack.yaml $(ACTONC_HS) $(ACTONLSP_HS) version.mk dist/builder $(ACTON_STACK_PREREQS) $(BDEPS) mkdir -p dist/bin - rm -f dist/bin/actonc + rm -f $(ACTONC) cd compiler && sed 's,^version: BUILD_VERSION,version: "$(VERSION)",' < lib/package.yaml.in > lib/package.yaml cd compiler && $(STACK) build acton lsp-server-acton $(ACTON_STACK_BUILD_OPTS) --ghc-options='-j4 $(ACTC_GHC_OPTS)' --dry-run 2>&1 | grep "Nothing to build" || \ (sed 's,^version: BUILD_VERSION,version: "$(VERSION_INFO)",' < acton/package.yaml.in > acton/package.yaml \ && sed 's,^version: BUILD_VERSION,version: "$(VERSION_INFO)",' < lsp-server/package.yaml.in > lsp-server/package.yaml \ - && unset CC && unset CXX && unset CFLAGS && unset CPPFLAGS && unset LDFLAGS && unset ACTON_REAL_LD && stack setup \ + && $(STACK) setup \ && $(STACK) build acton lsp-server-acton $(ACTON_STACK_BUILD_OPTS) --ghc-options='-j4 $(ACTC_GHC_OPTS)') cd compiler && $(STACK) --local-bin-path=../dist/bin install acton lsp-server-acton $(ACTON_STACK_BUILD_OPTS) --ghc-options='-j4 $(ACTC_GHC_OPTS)' # Keep actonc as a symlink for compatibility - ln -sf acton dist/bin/actonc + $(ACTONC_LINK_CMD) -dist/bin/actonc: dist/bin/acton +$(ACTONC): $(ACTON_BIN) @mkdir -p $(dir $@) - ln -sf acton $@ + $(ACTONC_LINK_CMD) + +$(LSP_SERVER): $(ACTON_BIN) + @if [ ! -f "$@" ]; then \ + echo "$@ missing; rebuilding $(ACTON_BIN)"; \ + $(MAKE) --always-make $(ACTON_BIN); \ + fi + test -f "$@" -dist/bin/lsp-server-acton: dist/bin/acton - @true +ifeq ($(OS),windows) +dist/bin/liblmdb.dll: $(ACTON_BIN) + mkdir -p dist/bin + powershell -NoProfile -ExecutionPolicy Bypass -Command '$$here = Get-Location; Set-Location compiler; $$programs = (stack path --programs).Trim(); Set-Location $$here; $$dll = Get-ChildItem -Path $$programs -Recurse -Filter liblmdb.dll | Select-Object -First 1; if ($$null -eq $$dll) { throw "liblmdb.dll not found below Stack programs dir" }; Copy-Item $$dll.FullName "$@" -Force' +endif .PHONY: clean-compiler clean-compiler: cd compiler && stack clean >/dev/null 2>&1 || true - rm -f dist/bin/acton dist/bin/actonc compiler/package.yaml compiler/acton.cabal \ + rm -f dist/bin/acton dist/bin/acton.exe dist/bin/actonc dist/bin/actonc.exe \ + dist/bin/lsp-server-acton dist/bin/lsp-server-acton.exe dist/bin/liblmdb.dll \ + compiler/package.yaml compiler/acton.cabal \ compiler/acton/package.yaml compiler/acton/acton.cabal \ compiler/lib/*.cabal compiler/acton/*.cabal compiler/lsp-server/*.cabal -ACTON_LINKAGE_BINS ?= dist/bin/acton dist/bin/lsp-server-acton dist/bin/actondb +ACTON_LINKAGE_BINS ?= $(ACTON_BIN) $(LSP_SERVER) $(ACTONDB) ACTON_ALLOWED_NEEDED_RE ?= ^(libc\.so\.6|libm\.so\.6|libdl\.so\.2|libpthread\.so\.0|librt\.so\.1|libutil\.so\.1|ld-linux-x86-64\.so\.2|ld-linux-aarch64\.so\.1)$$ .PHONY: ldd ldd: $(ACTON_LINKAGE_BINS) @@ -336,73 +384,83 @@ bdeps/out/lib/liblmdb.a: bdeps/lmdb/build.zig bdeps/lmdb/build.zig.zon $(DIST_ZI # /deps/libargp -------------------------------------------- LIBARGP_REF=137154fb257055beb11f3283021d8eccc3c4f470 LIBARGP_BUILD_ZIG=deps/libargp/build.zig +LIBARGP_BUILD_ZON=deps/libargp/build.zig.zon deps-download/$(LIBARGP_REF).tar.gz: mkdir -p deps-download $(CURL) -o $@ https://github.com/actonlang/argp-standalone/archive/$(LIBARGP_REF).tar.gz -dist/deps/libargp: deps-download/$(LIBARGP_REF).tar.gz $(LIBARGP_BUILD_ZIG) +dist/deps/libargp: deps-download/$(LIBARGP_REF).tar.gz $(LIBARGP_BUILD_ZIG) $(LIBARGP_BUILD_ZON) rm -rf "$@" mkdir -p "$@" cd "$@" && tar zx --strip-components=1 -f "$(TD)/$<" cp "$(TD)/$(LIBARGP_BUILD_ZIG)" "$@/build.zig" + cp "$(TD)/$(LIBARGP_BUILD_ZON)" "$@/build.zig.zon" rm -rf "$@/testsuite" touch "$(TD)/$@" # /deps/libbsdnt -------------------------------------------- LIBBSDNT_REF=cf7db3414867b8b4a5561bc9aa94a8050d0225c4 LIBBSDNT_BUILD_ZIG=deps/libbsdnt/build.zig +LIBBSDNT_BUILD_ZON=deps/libbsdnt/build.zig.zon deps-download/$(LIBBSDNT_REF).tar.gz: mkdir -p deps-download $(CURL) -o $@ https://github.com/actonlang/bsdnt/archive/$(LIBBSDNT_REF).tar.gz -dist/deps/libbsdnt: deps-download/$(LIBBSDNT_REF).tar.gz $(LIBBSDNT_BUILD_ZIG) +dist/deps/libbsdnt: deps-download/$(LIBBSDNT_REF).tar.gz $(LIBBSDNT_BUILD_ZIG) $(LIBBSDNT_BUILD_ZON) rm -rf "$@" mkdir -p "$@" cd "$@" && tar zx --strip-components=1 -f "$(TD)/$<" cp "$(TD)/$(LIBBSDNT_BUILD_ZIG)" "$@/build.zig" + cp "$(TD)/$(LIBBSDNT_BUILD_ZON)" "$@/build.zig.zon" touch "$(TD)/$@" # /deps/libgc -------------------------------------------- LIBGC_REF=0a23b211b558137de7ee654c5527a54113142517 LIBGC_BUILD_ZIG=deps/libgc/build.zig +LIBGC_BUILD_ZON=deps/libgc/build.zig.zon deps-download/$(LIBGC_REF).tar.gz: mkdir -p deps-download $(CURL) -o $@ https://github.com/bdwgc/bdwgc/archive/$(LIBGC_REF).tar.gz -dist/deps/libgc: deps-download/$(LIBGC_REF).tar.gz $(LIBGC_BUILD_ZIG) +dist/deps/libgc: deps-download/$(LIBGC_REF).tar.gz $(LIBGC_BUILD_ZIG) $(LIBGC_BUILD_ZON) rm -rf "$@" mkdir -p "$@" cd "$@" && tar zx --strip-components=1 -f "$(TD)/$<" cp "$(TD)/$(LIBGC_BUILD_ZIG)" "$@/build.zig" + cp "$(TD)/$(LIBGC_BUILD_ZON)" "$@/build.zig.zon" rm -rf "$@/tests" "$@/tools" touch "$(TD)/$@" # /deps/libmbedtls -------------------------------------------- LIBMBEDTLS_REF=c7d83538d3d359b05a9331bb2c9217977b5856ac LIBMBEDTLS_BUILD_ZIG=deps/mbedtls/build.zig +LIBMBEDTLS_BUILD_ZON=deps/mbedtls/build.zig.zon deps-download/$(LIBMBEDTLS_REF).tar.gz: mkdir -p deps-download $(CURL) -o $@ https://github.com/actonlang/mbedtls/archive/$(LIBMBEDTLS_REF).tar.gz -dist/deps/mbedtls: deps-download/$(LIBMBEDTLS_REF).tar.gz $(LIBMBEDTLS_BUILD_ZIG) +dist/deps/mbedtls: deps-download/$(LIBMBEDTLS_REF).tar.gz $(LIBMBEDTLS_BUILD_ZIG) $(LIBMBEDTLS_BUILD_ZON) rm -rf "$@" mkdir -p "$@" cd "$@" && tar zx --strip-components=1 -f "$(TD)/$<" cp "$(TD)/$(LIBMBEDTLS_BUILD_ZIG)" "$@/build.zig" + cp "$(TD)/$(LIBMBEDTLS_BUILD_ZON)" "$@/build.zig.zon" touch "$(TD)/$@" # /deps/libprotobuf_c -------------------------------------------- LIBPROTOBUF_C_REF=abc67a11c6db271bedbb9f58be85d6f4e2ea8389 LIBPROTOBUF_C_BUILD_ZIG=deps/libprotobuf_c/build.zig +LIBPROTOBUF_C_BUILD_ZON=deps/libprotobuf_c/build.zig.zon deps-download/$(LIBPROTOBUF_C_REF).tar.gz: mkdir -p deps-download $(CURL) -o $@ https://github.com/protobuf-c/protobuf-c/archive/$(LIBPROTOBUF_C_REF).tar.gz -dist/deps/libprotobuf_c: deps-download/$(LIBPROTOBUF_C_REF).tar.gz $(LIBPROTOBUF_C_BUILD_ZIG) +dist/deps/libprotobuf_c: deps-download/$(LIBPROTOBUF_C_REF).tar.gz $(LIBPROTOBUF_C_BUILD_ZIG) $(LIBPROTOBUF_C_BUILD_ZON) rm -rf "$@" mkdir -p "$@" cd "$@" && tar zx --strip-components=1 -f "$(TD)/$<" cp "$(TD)/$(LIBPROTOBUF_C_BUILD_ZIG)" "$@/build.zig" + cp "$(TD)/$(LIBPROTOBUF_C_BUILD_ZON)" "$@/build.zig.zon" touch "$(TD)/$@" # /deps/tlsuv --------------------------------------------- @@ -424,15 +482,17 @@ dist/deps/tlsuv: deps-download/$(TLSUV_REF).tar.gz dist/deps/libuv dist/deps/mbe # /deps/libutf8proc -------------------------------------- LIBUTF8PROC_REF=1cb28a66ca79a0845e99433fd1056257456cef8b LIBUTF8PROC_BUILD_ZIG=deps/libutf8proc/build.zig +LIBUTF8PROC_BUILD_ZON=deps/libutf8proc/build.zig.zon deps-download/$(LIBUTF8PROC_REF).tar.gz: mkdir -p deps-download $(CURL) -o $@ https://github.com/JuliaStrings/utf8proc/archive/$(LIBUTF8PROC_REF).tar.gz -dist/deps/libutf8proc: deps-download/$(LIBUTF8PROC_REF).tar.gz $(LIBUTF8PROC_BUILD_ZIG) +dist/deps/libutf8proc: deps-download/$(LIBUTF8PROC_REF).tar.gz $(LIBUTF8PROC_BUILD_ZIG) $(LIBUTF8PROC_BUILD_ZON) rm -rf "$@" mkdir -p "$@" cd "$@" && tar zx --strip-components=1 -f "$(TD)/$<" cp "$(TD)/$(LIBUTF8PROC_BUILD_ZIG)" "$@/build.zig" + cp "$(TD)/$(LIBUTF8PROC_BUILD_ZON)" "$@/build.zig.zon" touch "$(TD)/$@" # /deps/libuuid ------------------------------------------ @@ -443,58 +503,69 @@ dist/deps/libuuid: deps/libuuid # /deps/libuv -------------------------------------------- LIBUV_REF=d760a3f23511ebe7b1935fe1429147d4fca27bb4 LIBUV_BUILD_ZIG=deps/libuv/build.zig +LIBUV_BUILD_ZON=deps/libuv/build.zig.zon deps-download/$(LIBUV_REF).tar.gz: mkdir -p deps-download $(CURL) -o $@ https://github.com/actonlang/libuv/archive/$(LIBUV_REF).tar.gz -dist/deps/libuv: deps-download/$(LIBUV_REF).tar.gz $(LIBUV_BUILD_ZIG) +dist/deps/libuv: deps-download/$(LIBUV_REF).tar.gz $(LIBUV_BUILD_ZIG) $(LIBUV_BUILD_ZON) rm -rf "$@" mkdir -p "$@" cd "$@" && tar zx --strip-components=1 -f "$(TD)/$<" "libuv-$(LIBUV_REF)/include" "libuv-$(LIBUV_REF)/src" cp "$(TD)/$(LIBUV_BUILD_ZIG)" "$@/build.zig" + cp "$(TD)/$(LIBUV_BUILD_ZON)" "$@/build.zig.zon" touch "$(TD)/$@" # /deps/libxml2 ------------------------------------------ LIBXML2_REF=358ca4e6e34dd2b386aab1fdeb74a641c54940a0 LIBXML2_BUILD_ZIG=deps/libxml2/build.zig +LIBXML2_BUILD_ZON=deps/libxml2/build.zig.zon deps-download/$(LIBXML2_REF).tar.gz: mkdir -p deps-download $(CURL) -o $@ https://github.com/actonlang/libxml2/archive/$(LIBXML2_REF).tar.gz -dist/deps/libxml2: deps-download/$(LIBXML2_REF).tar.gz $(LIBXML2_BUILD_ZIG) +dist/deps/libxml2: deps-download/$(LIBXML2_REF).tar.gz $(LIBXML2_BUILD_ZIG) $(LIBXML2_BUILD_ZON) rm -rf "$@" mkdir -p "$@" - cd "$@" && tar zx --strip-components=1 -f "$(TD)/$<" + cd "$@" && tar zx --strip-components=1 \ + --exclude='*/doc' --exclude='*/example' --exclude='*/fuzz' \ + --exclude='*/os400' --exclude='*/python' --exclude='*/test*' \ + -f "$(TD)/$<" cp "$(TD)/$(LIBXML2_BUILD_ZIG)" "$@/build.zig" + cp "$(TD)/$(LIBXML2_BUILD_ZON)" "$@/build.zig.zon" rm -rf "$@/doc" "$@/example" "$@/fuzz" "$@/os400" "$@/python" $@/test* touch "$(TD)/$@" # /deps/pcre2 -------------------------------------------- LIBPCRE2_REF=e4ccef3034c870342f2d37c928e98bc8c69cd340 LIBPCRE2_BUILD_ZIG=deps/pcre2/build.zig +LIBPCRE2_BUILD_ZON=deps/pcre2/build.zig.zon deps-download/$(LIBPCRE2_REF).tar.gz: mkdir -p deps-download $(CURL) -o $@ https://github.com/PCRE2Project/pcre2/archive/$(LIBPCRE2_REF).tar.gz -dist/deps/pcre2: deps-download/$(LIBPCRE2_REF).tar.gz $(LIBPCRE2_BUILD_ZIG) +dist/deps/pcre2: deps-download/$(LIBPCRE2_REF).tar.gz $(LIBPCRE2_BUILD_ZIG) $(LIBPCRE2_BUILD_ZON) rm -rf "$@" mkdir -p "$@" cd "$@" && tar zx --strip-components=1 -f "$(TD)/$<" cp "$(TD)/$(LIBPCRE2_BUILD_ZIG)" "$@/build.zig" + cp "$(TD)/$(LIBPCRE2_BUILD_ZON)" "$@/build.zig.zon" touch "$(TD)/$@" # /deps/libsnappy_c -------------------------------------------- LIBSNAPPY_C_REF=dc05e026488865bc69313a68bcc03ef2e4ea8e83 LIBSNAPPY_C_BUILD_ZIG=deps/libsnappy_c/build.zig +LIBSNAPPY_C_BUILD_ZON=deps/libsnappy_c/build.zig.zon deps-download/$(LIBSNAPPY_C_REF).tar.gz: mkdir -p deps-download $(CURL) -o $@ https://github.com/google/snappy/archive/$(LIBSNAPPY_C_REF).tar.gz -dist/deps/libsnappy_c: deps-download/$(LIBSNAPPY_C_REF).tar.gz $(LIBSNAPPY_C_BUILD_ZIG) +dist/deps/libsnappy_c: deps-download/$(LIBSNAPPY_C_REF).tar.gz $(LIBSNAPPY_C_BUILD_ZIG) $(LIBSNAPPY_C_BUILD_ZON) rm -rf "$@" mkdir -p "$@" cd "$@" && tar zx --strip-components=1 -f "$(TD)/$<" cp "$(TD)/$(LIBSNAPPY_C_BUILD_ZIG)" "$@/build.zig" + cp "$(TD)/$(LIBSNAPPY_C_BUILD_ZON)" "$@/build.zig.zon" touch "$(TD)/$@" dist/deps/libnetstring: deps/libnetstring $(DIST_ZIG) @@ -512,7 +583,7 @@ dist/deps/libyyjson: deps/libyyjson $(DIST_ZIG) # archives must exist first. (test, test-stdlib, test-incremental, online-tests # already pull it in transitively via dist/bin/acton[c].) test-builtins test-compiler test-lib-accept test-acton-goldens-accept test-cross-compile test-syntaxerrors test-syntaxerrors-accept test-typeerrors test-typeerrors-accept test-db test-examples test-lang test-regressions test-rts: $(BDEPS) -test: dist/bin/acton +test: $(ACTON_BIN) cd compiler && stack test libacton acton:test_acton acton:incremental $(MAKE) test-stdlib $(MAKE) -C backend test @@ -538,11 +609,11 @@ test-goldens-accept: test-compiler-accept test-incremental-accept test-syntaxerr test-cross-compile: cd compiler && stack test acton --ta '-p "cross-compilation"' -test-incremental: dist/bin/actonc +test-incremental: $(ACTONC) cd compiler && stack test acton:incremental .PHONY: test-incremental-accept -test-incremental-accept: dist/bin/actonc +test-incremental-accept: $(ACTONC) cd compiler && stack test acton:incremental --ta "--accept" .PHONY: test-rebuild test-rebuild-accept @@ -580,12 +651,12 @@ test-rts: test-rts-db: $(MAKE) -C test -test-stdlib: dist/bin/acton dist/std +test-stdlib: $(ACTON_BIN) dist/std cd compiler && stack test acton --ta '-p "stdlib"' $(MAKE) -C test tls-test-server cd test/stdlib_tests && "$(ACTON)" test -online-tests: dist/bin/actonc +online-tests: $(ACTONC) cd compiler && stack test acton:test_acton_online @@ -608,24 +679,27 @@ clean-bdeps: # BACKEND_FILES = backend/Build.act backend/build.zig backend/build.zig.zon $(wildcard backend/*.c backend/*.h backend/failure_detector/*.c backend/failure_detector/*.h) -DIST_BACKEND_FILES = $(addprefix dist/,$(BACKEND_FILES)) dist/backend/deps dist/bin/actondb +DIST_BACKEND_FILES = $(addprefix dist/,$(BACKEND_FILES)) dist/backend/deps +ifneq ($(OS),windows) +DIST_BACKEND_FILES += $(ACTONDB) +endif dist/backend%: backend/% mkdir -p "$(dir $@)" cp -a "$<" "$@" .PHONY: dist/base -dist/base: base base/.build base/__root.zig base/acton.zig base/build.zig base/build.zig.zon base/acton.zig dist/bin/actonc $(DEPS) dist/backend/Build.act +dist/base: base base/.build base/__root.zig base/acton.zig base/build.zig base/build.zig.zon base/acton.zig $(ACTONC) $(DEPS) dist/backend/Build.act mkdir -p "$@" "$@/.build" "$@/out" rm -rf "$@/src" "$@/out/types/std" cp -a base/__root.zig base/Build.act base/acton.zig base/build.zig base/build.zig.zon base/builtin base/rts base/src dist/base/ - cd dist/base && ../bin/actonc build --skip-build && rm -rf .build + cd dist/base && ../bin/actonc$(EXE_SUFFIX) build --skip-build && rm -rf .build .PHONY: dist/std -dist/std: std std/Build.act std/build.zig std/build.zig.zon dist/base dist/bin/actonc $(DEPS) +dist/std: std std/Build.act std/build.zig std/build.zig.zon dist/base $(ACTONC) $(DEPS) mkdir -p "$@" "$@/.build" "$@/out" rm -rf "$@/src" "$@/out/types" cp -a std/Build.act std/build.zig std/build.zig.zon std/src dist/std/ - cd dist/std && ../bin/actonc build --skip-build && rm -rf .build + cd dist/std && ../bin/actonc$(EXE_SUFFIX) build --skip-build && rm -rf .build # This does a little hack, first copying and then moving the file in place. This # is to avoid an error if the executable is currently running. cp tries to open @@ -637,9 +711,9 @@ ifeq ($(OS),macos) # /dev/null to prevent Zig from trying to use them and instead falling back to # its own implementation. # See https://codeberg.org/ziglang/zig/issues/31658. -dist/bin/actondb: export DEVELOPER_DIR := $(or $(DEVELOPER_DIR),/dev/null) +$(ACTONDB): export DEVELOPER_DIR := $(or $(DEVELOPER_DIR),/dev/null) endif -dist/bin/actondb: $(DIST_ZIG) $(DEPS) +$(ACTONDB): $(DIST_ZIG) $(DEPS) @mkdir -p $(dir $@) cd dist/backend && "$(ZIG)" build -Donly_actondb $(if $(ACTON_ZIG_TARGET),-Dtarget=$(ACTON_ZIG_TARGET)) --prefix "$(TD)/dist" @@ -660,7 +734,11 @@ dist/completion/acton.bash-completion: completion/acton.bash-completion mkdir -p "$(dir $@)" cp "$<" "$@" +ifeq ($(OS),windows) +ZIG_TARBALL=zig-$(ARCH)-windows-$(ZIG_VERSION).zip +else ZIG_TARBALL=zig-$(ARCH)-$(OS)-$(ZIG_VERSION).tar.xz +endif ZIG_DOWNLOAD_BASE_URL ?= ifeq ($(strip $(ZIG_DOWNLOAD_BASE_URL)),) ifeq ($(findstring -dev,$(ZIG_VERSION)),-dev) @@ -674,7 +752,16 @@ endif dist/zig: deps-download/$(ZIG_TARBALL) mkdir -p "$@" +ifeq ($(OS),windows) + rm -rf "$@.tmp" + mkdir -p "$@.tmp" + unzip -q "$(TD)/$<" -d "$@.tmp" + rm -rf "$@" + mv "$@.tmp"/zig-* "$@" + rm -rf "$@.tmp" +else cd "$@" && tar Jx --strip-components=1 -f "../../$^" +endif rm -rf "$@/doc" cp -a deps/zig-extras/* "$@" diff --git a/compiler/acton/Main.hs b/compiler/acton/Main.hs index 14083218a..837d48403 100644 --- a/compiler/acton/Main.hs +++ b/compiler/acton/Main.hs @@ -63,7 +63,7 @@ import Data.Word (Word32) import Data.Graph import Data.String.Utils (replace) import Data.Version (showVersion) -import Data.Char (toLower) +import Data.Char (isAlpha, toLower) import qualified Data.List import Data.Either (partitionEithers) import qualified Data.Map as M @@ -173,8 +173,14 @@ optimizeModeToZig C.ReleaseSmall = "ReleaseSmall" optimizeModeToZig C.ReleaseFast = "ReleaseFast" zig :: Paths -> FilePath -zig paths = sysPath paths ++ "/zig/zig" +zig paths = sysPath paths ++ "/zig/" ++ zigExeName +zigExeName :: FilePath +#if defined(mingw32_HOST_OS) +zigExeName = "zig.exe" +#else +zigExeName = "zig" +#endif -- Try to acquire a lock, return Nothing if failed, Just (FileLock, FilePath) if succeeded tryLock :: FilePath -> IO (Maybe (FileLock, FilePath)) @@ -1936,7 +1942,7 @@ runCliPostCompile cliHooks gopts plan env = do | otherwise = [binTask] preTestBinTasks = map (\t -> BinTask True (modNameToString (name t)) (A.GName (name t) (A.name "test_main")) True) rootTasks selectedTasksByProj = selectedTasksWithProvidersByProj globalTasks neededTasks - depModuleOptsByProj = depModuleOptionsByProj selectedTasksByProj projMap + depModuleOptsByProj = depModuleOptionsByProj sysAbs selectedTasksByProj projMap rootSelectedModuleEntries = selectedCSourceEntriesForProj rootProj (M.findWithDefault [] rootProj selectedTasksByProj) rootBuildLibraries = explicitBuildLibraries rootSelectedModuleEntries rootSpec rootModuleEntries = excludeLibrarySources rootBuildLibraries rootSelectedModuleEntries @@ -2046,15 +2052,18 @@ selectedRootStubEntriesForBins paths bins = | b <- bins ] --- | For each consuming project, map dep name to selected C-source CSV. -depModuleOptionsByProj :: M.Map FilePath [GlobalTask] -> M.Map FilePath ProjCtx -> M.Map FilePath (M.Map String String) -depModuleOptionsByProj tasksByProj projMap = +-- | For each consuming project, map generated Acton dep name to selected C-source CSV. +depModuleOptionsByProj :: FilePath -> M.Map FilePath [GlobalTask] -> M.Map FilePath ProjCtx -> M.Map FilePath (M.Map String String) +depModuleOptionsByProj sysAbs tasksByProj projMap = M.map mkForProj projMap where + sysRoot = addTrailingPathSeparator sysAbs + isSysProj p = p == sysAbs || sysRoot `isPrefixOf` p mkForProj ctx = M.fromList [ (depName, mkCsv depProj) | (depName, depProj) <- projDeps ctx + , not (isSysProj depProj) ] mkCsv depProj = intercalate "," (selectedCSourceEntriesForProj depProj (M.findWithDefault [] depProj tasksByProj)) @@ -2399,7 +2408,7 @@ generateFingerprint name = do genBuildZigFiles :: BuildSpec.BuildSpec -> M.Map String BuildSpec.PkgDep -> [(String, FilePath)] -> Paths -> M.Map String String -> M.Map String FilePath -> IO () genBuildZigFiles spec rootPins depOverrides paths depModuleOpts depPathOverrides = do let proj = projPath paths - projAbs <- canonicalizePath proj + projAbs <- normalizePathSafe proj let sys = sysPath paths buildZigPath = joinPath [proj, "build.zig"] buildZonPath = joinPath [proj, "build.zig.zon"] @@ -2410,8 +2419,8 @@ genBuildZigFiles spec rootPins depOverrides paths depModuleOpts depPathOverrides let zonName = BuildSpec.specName spec fp = BuildSpec.fingerprint spec (transPkgs, transZigs) <- collectDepsRecursive spec proj rootPins depOverrides - absSys <- canonicalizePath sys - let relSys = relativeViaRoot projAbs absSys + absSys <- normalizePathSafe sys + let relSys = zonPath (relativeViaRoot projAbs absSys) homeDir <- getHomeDirectory depsRootAbs <- normalizePathSafe (joinPath [homeDir, ".cache", "acton", "deps"]) normalizedSpec <- normalizeSpecPaths proj spec @@ -2557,16 +2566,22 @@ genBuildZig template spec zigDeps depModuleOpts = in unlines $ header ++ concatMap inject (lines template) where pkgDepDef (name, _) = - let selectedCsv = M.findWithDefault "" name depModuleOpts - in unlines [ " const actdep_" ++ name ++ " = b.dependency(\"" ++ name ++ "\", .{" - , " .target = target," - , " .optimize = optimize," - , " .no_threads = no_threads," - , " .db = db," - , " .acton_modules = " ++ show selectedCsv ++ "," - , " .acton_root_stubs = \"\"," - , " });" - ] + let actonProjectArgs = + case M.lookup name depModuleOpts of + Nothing -> [] + Just selectedCsv -> + [ " .acton_modules = " ++ show selectedCsv ++ "," + , " .acton_root_stubs = \"\"," + ] + in unlines $ + [ " const actdep_" ++ name ++ " = b.dependency(\"" ++ name ++ "\", .{" + , " .target = target," + , " .optimize = optimize," + , " .no_threads = no_threads," + , " .db = db," + ] + ++ actonProjectArgs + ++ [ " });" ] pkgLibLink (name, _) = " libActonProject.root_module.linkLibrary(actdep_" ++ name ++ ".artifact(\"ActonProject\"));\n" ++ " for (explicit_static_libraries.items) |libActonExplicit| {\n" @@ -2592,6 +2607,15 @@ genBuildZig template spec zigDeps depModuleOpts = zigExeLink resolved = concat [ " executable.root_module.linkLibrary(dep_" ++ zigDepResolvedVarName resolved ++ ".artifact(\"" ++ art ++ "\"));\n" | art <- BuildSpec.artifacts (zigDepResolvedDep resolved) ] +zonPath :: FilePath -> String +zonPath = concatMap escapeChar . map slash + where + slash '\\' = '/' + slash c = c + + escapeChar '"' = "\\\"" + escapeChar c = [c] + genBuildZigZon :: String -> String -> FilePath -> FilePath -> String -> String -> BuildSpec.BuildSpec -> [ZigDepResolved] -> String genBuildZigZon template relSys depsRootAbs projAbs fingerprint zonName spec zigDepsResolved = let @@ -2620,7 +2644,7 @@ genBuildZigZon template relSys depsRootAbs projAbs fingerprint zonName spec zigD if isAbsolutePath rawPath then normalise rawPath else normalise (rebasePath projRoot rawPath) - path = relativeViaRoot projRoot pathAbs + path = zonPath (relativeViaRoot projRoot pathAbs) in unlines [ " ." ++ name ++ " = .{" , " .path = \"" ++ path ++ "\"," , " }," @@ -2632,7 +2656,7 @@ genBuildZigZon template relSys depsRootAbs projAbs fingerprint zonName spec zigD if isAbsolutePath p then normalise p else normalise (rebasePath projAbs p) - relPath = relativeViaRoot projAbs absPath + relPath = zonPath (relativeViaRoot projAbs absPath) in unlines [ " ." ++ zigDepResolvedPkgName resolved ++ " = .{" , " .path = \"" ++ relPath ++ "\"," , " }," @@ -2784,18 +2808,24 @@ relativeViaRoot :: FilePath -> FilePath -> FilePath relativeViaRoot baseAbs targetAbs | not (isAbsolutePath targetAbs) = targetAbs | otherwise = - let (bDriveRaw, bPath) = splitDrive (normalise baseAbs) - (tDriveRaw, tPath) = splitDrive (normalise targetAbs) - bDrive = map toLower bDriveRaw - tDrive = map toLower tDriveRaw - in if bDrive /= tDrive && (not (null bDrive) || not (null tDrive)) - then makeRelativeOrAbsolute baseAbs targetAbs + let bParts = cleanParts (normalise baseAbs) + tParts = cleanParts (normalise targetAbs) + bDrive = pathDrive bParts + tDrive = pathDrive tParts + in if bDrive /= tDrive + then normalise targetAbs else - let ups = replicate (length (cleanParts bPath)) ".." - tParts = cleanParts tPath - in joinPath (ups ++ tParts) + let common = length (takeWhile (uncurry (==)) (zip bParts tParts)) + ups = replicate (length bParts - common) ".." + relParts = ups ++ drop common tParts + in if null relParts then "." else joinPath relParts where cleanParts = filter (\c -> not (null c) && c /= "/") . splitDirectories + pathDrive (p:_) + | isDriveComponent p = Just (map toLower p) + pathDrive _ = Nothing + isDriveComponent [c, ':'] = isAlpha c + isDriveComponent _ = False -- | Walk BuildSpec dependencies to collect transitive packages and zig deps. collectDepsRecursive :: BuildSpec.BuildSpec -> FilePath -> M.Map String BuildSpec.PkgDep -> [(String, FilePath)] -> IO (M.Map String BuildSpec.PkgDep, [ZigDepRef]) diff --git a/compiler/acton/PkgCommands.hs b/compiler/acton/PkgCommands.hs index c78fd70bb..5540e844e 100644 --- a/compiler/acton/PkgCommands.hs +++ b/compiler/acton/PkgCommands.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-# LANGUAGE OverloadedStrings #-} module PkgCommands ( installCommand @@ -981,7 +982,14 @@ getZigExe :: IO FilePath getZigExe = do execDir <- takeDirectory <$> getExecutablePath sysPath <- canonicalizePath (execDir "..") - return (sysPath "zig" "zig") + return (sysPath "zig" zigExeName) + +zigExeName :: FilePath +#if defined(mingw32_HOST_OS) +zigExeName = "zig.exe" +#else +zigExeName = "zig" +#endif validateDepName :: String -> IO () validateDepName name = diff --git a/compiler/acton/package.yaml.in b/compiler/acton/package.yaml.in index 271656edd..a639dfdd0 100644 --- a/compiler/acton/package.yaml.in +++ b/compiler/acton/package.yaml.in @@ -81,6 +81,9 @@ executables: # default linker instead of breaking. The script branches internally: GNU ld # -Bstatic toggling on Linux, an -lfoo -> libfoo.a rewrite for ld64 on macOS. when: + - condition: os(windows) + ghc-options: + - -fno-PIC - condition: os(linux) # -no-pie is required on Linux but invalid on macOS (executables are PIE). ghc-options: diff --git a/compiler/lib/package.yaml.in b/compiler/lib/package.yaml.in index 593fa821f..0fc8609c4 100644 --- a/compiler/lib/package.yaml.in +++ b/compiler/lib/package.yaml.in @@ -66,6 +66,8 @@ library: - condition: os(windows) c-sources: - cbits/wcwidth.c + ghc-options: + - -fno-PIC exposed-modules: - Acton.Boxing - Acton.BuildSpec diff --git a/compiler/lib/src/Acton/CommandLineParser.hs b/compiler/lib/src/Acton/CommandLineParser.hs index c3fd5a297..bc2c80c6c 100644 --- a/compiler/lib/src/Acton/CommandLineParser.hs +++ b/compiler/lib/src/Acton/CommandLineParser.hs @@ -209,6 +209,13 @@ data ZigPkgRemoveOptions = ZigPkgRemoveOptions -------------------------------------------------------------------- -- Internal stuff +fileCompleter :: String -> Mod ArgumentFields String +#if defined(mingw32_HOST_OS) +fileCompleter _ = mempty +#else +fileCompleter cmd = completer (bashCompleter cmd) +#endif + cmdLineParser :: Parser CmdLineOptions cmdLineParser = hsubparser ( command "new" (info (CmdOpt <$> globalOptions <*> (New <$> newOptions)) (progDesc "Create a new Acton project")) @@ -225,7 +232,7 @@ cmdLineParser = hsubparser <> command "doc" (info (CmdOpt <$> globalOptions <*> (Doc <$> docOptions)) (progDesc "Show type and docstring info")) <> command "version" (info (CmdOpt <$> globalOptions <*> pure Version) (progDesc "Show version")) ) - <|> (CompileOpt <$> (fmap (:[]) $ argument str (metavar "ACTONFILE" <> help "Compile Acton file" <> completer (bashCompleter "file -X '!*.act' -o plusdirs"))) <*> globalOptions <*> compileOptions) + <|> (CompileOpt <$> (fmap (:[]) $ argument str (metavar "ACTONFILE" <> help "Compile Acton file" <> fileCompleter "file -X '!*.act' -o plusdirs")) <*> globalOptions <*> compileOptions) globalOptions :: Parser GlobalOptions globalOptions = GlobalOptions @@ -451,7 +458,7 @@ cloudOptions = CloudOptions <*> switch (long "stop" <> help "Help stop!") docOptions = DocOptions - <$> (argument str (metavar "FILE" <> help "Input file (.act or .tydb) - optional in projects" <> completer (bashCompleter "file -X '!*.act' -X '!*.tydb' -o plusdirs")) <|> pure "") + <$> (argument str (metavar "FILE" <> help "Input file (.act or .tydb) - optional in projects" <> fileCompleter "file -X '!*.act' -X '!*.tydb' -o plusdirs") <|> pure "") <*> formatFlags <*> optional (strOption (long "output" <> short 'o' <> metavar "FILE" <> help "Output file (default: stdout)")) where diff --git a/compiler/lib/src/Acton/Compile.hs b/compiler/lib/src/Acton/Compile.hs index 823c898ae..0cd2bf7c4 100644 --- a/compiler/lib/src/Acton/Compile.hs +++ b/compiler/lib/src/Acton/Compile.hs @@ -1205,7 +1205,7 @@ buildGlobalTasks sp gopts opts projMap mSeeds = do seedKeys <- case mSeeds of Nothing -> return allKeys Just files -> do - absFiles <- mapM canonicalizePath files + absFiles <- mapM normalizePathSafe files let pathIndex = M.fromList [ (actFile, TaskKey (projRoot ctx) mn) | (ctx, mods) <- perProj, (actFile, mn) <- mods ] found = mapMaybe (`M.lookup` pathIndex) absFiles return (if null found then allKeys else found) @@ -1253,7 +1253,7 @@ selectNeededTasks pathsRoot rootProj globalTasks srcFiles = do return [ t | t <- globalTasks, Data.Set.member (gtKey t) neededKeys ] where lookupTaskKey ts f = do - absF <- canonicalizePath f + absF <- normalizePathSafe f let byPath = listToMaybe [ gtKey t | t <- ts , let k = gtKey t @@ -3138,6 +3138,13 @@ systemTypePaths :: FilePath -> FilePath -> [FilePath] systemTypePaths sys types = [joinPath [sys, "std", "out", "types"], types] +zigExeName :: FilePath +#if defined(mingw32_HOST_OS) +zigExeName = "zig.exe" +#else +zigExeName = "zig" +#endif + type FingerprintMap = M.Map String FilePath scratchBuildSpec :: FilePath -> BuildSpec.BuildSpec @@ -3363,9 +3370,10 @@ withProjectLock projDir action = -- Resolves the project root (or temp root), output dirs, and search path, -- creating required directories along the way. findPaths :: FilePath -> C.CompileOptions -> IO Paths -findPaths actFile opts = do execDir <- takeDirectory <$> getExecutablePath - sysPath <- canonicalizePath (if null $ C.syspath opts then execDir ++ "/.." else C.syspath opts) - absSrcFile <- canonicalizePath actFile +findPaths actFile opts = do execPath <- normalisePathSyntax <$> getExecutablePath + let execDir = takeDirectory execPath + sysPath <- canonicalizePathPosix (if null $ C.syspath opts then execDir ++ "/.." else C.syspath opts) + absSrcFile <- canonicalizePathPosix actFile (isTmp, projPath, dirInSrc) <- analyze (takeDirectory absSrcFile) [] let sysTypes = joinPath [sysPath, "base", "out", "types"] srcDir = if isTmp then takeDirectory absSrcFile else joinPath [projPath, "src"] @@ -3384,16 +3392,19 @@ findPaths actFile opts = do execDir <- takeDirectory <$> getExecutablePath return $ Paths sPaths sysPath sysTypes projPath projOut projTypes binDir srcDir isTmp fileExt modName where (fileBody,fileExt) = splitExtension $ takeFileName actFile - analyze "/" ds = do tmp <- canonicalizePath (C.tempdir opts) - return (True, tmp, []) - analyze pre ds = do isProjectRoot <- isActonProjectRoot pre - if isProjectRoot - then case ds of - [] -> return $ (False, pre, []) - "src":dirs -> return $ (False, pre, dirs) - "out":"types":dirs -> return $ (False, pre, dirs) - _ -> throwProjectError ("Source file is not in a valid project directory: " ++ joinPath ds) - else analyze (takeDirectory pre) (takeFileName pre : ds) + analyze pre ds + | pre == takeDirectory pre = do + tmp <- canonicalizePathPosix (C.tempdir opts) + return (True, tmp, []) + | otherwise = do + isProjectRoot <- isActonProjectRoot pre + if isProjectRoot + then case ds of + [] -> return $ (False, pre, []) + "src":dirs -> return $ (False, pre, dirs) + "out":"types":dirs -> return $ (False, pre, dirs) + _ -> throwProjectError ("Source file is not in a valid project directory: " ++ joinPath ds) + else analyze (takeDirectory pre) (takeFileName pre : ds) -- Module helpers for multi-project builds --------------------------------------------------------- @@ -3630,12 +3641,24 @@ isAbsolutePath p = (c:':':_) -> isAlpha c _ -> False +-- This module intentionally uses POSIX FilePath operations for Acton module +-- paths. Canonical paths from Windows use backslashes, so normalize them at the +-- boundary before takeDirectory, makeRelative, splitDirectories, etc. see them. +normalisePathSyntax :: FilePath -> FilePath +normalisePathSyntax = normalise . map slash + where + slash '\\' = '/' + slash c = c + +canonicalizePathPosix :: FilePath -> IO FilePath +canonicalizePathPosix p = normalisePathSyntax <$> canonicalizePath p + -- | Normalize a path without failing if it does not exist. -- Falls back to normalise for non-existent paths (useful for temporary builds). normalizePathSafe :: FilePath -> IO FilePath normalizePathSafe p = do res <- try (canonicalizePath p) :: IO (Either IOException FilePath) - return $ either (const (normalise p)) id res + return $ normalisePathSyntax (either (const p) id res) -- | Trim leading and trailing whitespace from a string. -- Used when parsing or normalizing BuildSpec inputs. @@ -3723,7 +3746,7 @@ fetchDependencies gopts paths depOverrides = do unless (C.quiet gopts) $ putStrLn "Resolving dependencies (fetching if missing)..." home <- getHomeDirectory - let zigExe = joinPath [sysPath paths, "zig", "zig"] + let zigExe = joinPath [sysPath paths, "zig", zigExeName] globalCache = joinPath [home, ".cache", "acton", "zig-global-cache"] depsCache = joinPath [home, ".cache", "acton", "deps"] cacheDir h = joinPath [globalCache, "p", h] diff --git a/compiler/lib/src/InterfaceFiles.hs b/compiler/lib/src/InterfaceFiles.hs index 72a4db190..90d128eff 100644 --- a/compiler/lib/src/InterfaceFiles.hs +++ b/compiler/lib/src/InterfaceFiles.hs @@ -110,7 +110,7 @@ import Foreign.Ptr (castPtr) import Foreign.Storable (peek) import GHC.Generics (Generic) import System.Directory (canonicalizePath, createDirectoryIfMissing, doesDirectoryExist, doesFileExist, getFileSize, getModificationTime, getPermissions, listDirectory, removeFile, removePathForcibly, writable) -import System.FilePath ((), takeExtension) +import System.FilePath ((), normalise, takeExtension) import System.IO.Error (isDoesNotExistError) import System.IO.Unsafe (unsafePerformIO) #if !defined(mingw32_HOST_OS) @@ -315,9 +315,10 @@ copyVal (LMDB.MDB_val len ptr) = withEnv :: FilePath -> Bool -> Int -> (LMDB.MDB_env -> IO a) -> IO a withEnv path readOnly mapSize action = - withInterfaceLock path $ + withInterfaceLock lmdbPath $ E.bracket open LMDB.mdb_env_close action where + lmdbPath = normalise path open = withMVar lmdbOpenLock $ \_ -> do env <- LMDB.mdb_env_create (do LMDB.mdb_env_set_mapsize env mapSize @@ -326,9 +327,17 @@ withEnv path readOnly mapSize action = `E.onException` LMDB.mdb_env_close env openEnv env | readOnly = do - flags <- readOnlyOpenFlags path - LMDB.mdb_env_open env path flags - | otherwise = LMDB.mdb_env_open env path [] + exists <- doesDirectoryExist lmdbPath + unless exists $ + ioError (userError ("Missing .tydb directory: " ++ lmdbPath)) + flags <- readOnlyOpenFlags lmdbPath + openWithContext env "read" flags + | otherwise = do + createDirectoryIfMissing True lmdbPath + openWithContext env "write" [] + openWithContext env mode flags = + LMDB.mdb_env_open env lmdbPath flags `E.catch` \(err :: LMDB.LMDB_Error) -> + ioError (userError ("mdb_env_open " ++ mode ++ " " ++ lmdbPath ++ ": " ++ show err)) withInterfaceLock :: FilePath -> IO a -> IO a withInterfaceLock path action = do diff --git a/compiler/lib/src/Utils.hs b/compiler/lib/src/Utils.hs index e4962a564..a30103831 100644 --- a/compiler/lib/src/Utils.hs +++ b/compiler/lib/src/Utils.hs @@ -25,8 +25,8 @@ import SrcLocation import Pretty import Control.DeepSeq import Prelude hiding((<>)) -import System.Directory (renameFile, removeFile) -import System.FilePath (takeDirectory, takeFileName) +import System.Directory (createDirectoryIfMissing, renameFile, removeFile) +import System.FilePath (normalise, takeDirectory, takeFileName) import System.IO (openTempFile, hSetEncoding, utf8, hPutStr, hClose) import System.IO.Error (catchIOError) @@ -173,7 +173,8 @@ iff False _ = return () -- | Write a UTF-8 text file atomically (temp file + rename). -- Keeps readers from observing partial writes of generated artifacts. writeFileUtf8Atomic :: FilePath -> String -> IO () -writeFileUtf8Atomic path contents = +writeFileUtf8Atomic path contents = do + createDirectoryIfMissing True dir Control.Exception.bracketOnError (openTempFile dir template) (\(tmp, h) -> do @@ -183,7 +184,8 @@ writeFileUtf8Atomic path contents = hSetEncoding h utf8 hPutStr h contents hClose h - renameFile tmp path) + renameFile tmp path') where - dir = takeDirectory path - template = takeFileName path ++ ".tmp" + path' = normalise path + dir = takeDirectory path' + template = takeFileName path' ++ ".tmp" diff --git a/compiler/stack.yaml b/compiler/stack.yaml index 6a856ac03..86d79c979 100644 --- a/compiler/stack.yaml +++ b/compiler/stack.yaml @@ -71,5 +71,7 @@ extra-lib-dirs: # system-ghc: true ghc-options: - "$everything": -fPIC + # Keep the Linux static-link workaround scoped to the unix package. Applying + # -fPIC broadly makes GHC 9.8.4 Windows builds crash in generated code. + unix: -fPIC lsp-types: -O0 -fexternal-interpreter diff --git a/compiler/stack.yaml.lock b/compiler/stack.yaml.lock index 9edc1feb5..1c17a638a 100644 --- a/compiler/stack.yaml.lock +++ b/compiler/stack.yaml.lock @@ -1,7 +1,7 @@ # This file was autogenerated by Stack. # You should not edit this file by hand. # For more information, please see the documentation at: -# https://docs.haskellstack.org/en/stable/topics/lock_files +# https://docs.haskellstack.org/en/stable/lock_files packages: - completed: @@ -39,6 +39,13 @@ packages: size: 3998 original: hackage: filepath-1.4.300.1 +- completed: + hackage: fsnotify-0.3.0.1@sha256:58bb530d7acf93eb4ed69473e32a1485581815f04f69dfc8a278523781ba49dd,2988 + pantry-tree: + sha256: 8d1a410d34fd77e9b6603c9d1a940d7de63a85c852dbde467c0ef38c82ef6ea0 + size: 1130 + original: + hackage: fsnotify-0.3.0.1@sha256:58bb530d7acf93eb4ed69473e32a1485581815f04f69dfc8a278523781ba49dd,2988 - completed: hackage: lsp-2.7.0.0@sha256:cec57fe8924368ab3f431726b0ca0c9d76fad025161fc3d2fde71ed814d0bf7e,3998 pantry-tree: @@ -74,6 +81,13 @@ packages: size: 1944 original: hackage: tasty-1.5.3 +- completed: + hackage: time-1.12.2@sha256:88e8493d9130038d3b9968a2530a0900141cd3d938483c83dde56e12b875ebc8,6510 + pantry-tree: + sha256: de0ab314661da3788b5dad20254e44929b1659b00d32b5a0cd54922a05e006e8 + size: 7264 + original: + hackage: time-1.12.2 - completed: hackage: unix-2.8.2.1@sha256:433396e6087bbe6433ea4a743600bddd29d0e6cd701ea688d6d637ebae7b7bac,9318 pantry-tree: @@ -81,6 +95,13 @@ packages: size: 5752 original: hackage: unix-2.8.2.1 +- completed: + hackage: Win32-2.14.2.2@sha256:534f6273a3a61162747111bb5cbdda7f04081971083c69ed4562eb34c744d845,5877 + pantry-tree: + sha256: 726de3917a588afc73c0af619b5280594fa22409fc871e20ad7b2b16e0402e9c + size: 8301 + original: + hackage: Win32-2.14.2.2 snapshots: - completed: sha256: 7e724f347d5969cb5e8dde9f9aae30996e3231c29d1dafd45f21f1700d4c4fcb diff --git a/deps/libargp/build.zig.zon b/deps/libargp/build.zig.zon new file mode 100644 index 000000000..8f7872ce2 --- /dev/null +++ b/deps/libargp/build.zig.zon @@ -0,0 +1,8 @@ +.{ + .name = .libargp, + .version = "0.0.0", + .fingerprint = 0xb32523545ff94c96, + .minimum_zig_version = "0.16.0", + .dependencies = .{}, + .paths = .{""}, +} diff --git a/deps/libbsdnt/build.zig.zon b/deps/libbsdnt/build.zig.zon new file mode 100644 index 000000000..ad82e3ae0 --- /dev/null +++ b/deps/libbsdnt/build.zig.zon @@ -0,0 +1,8 @@ +.{ + .name = .libbsdnt, + .version = "0.0.0", + .fingerprint = 0xc0c476554a015af3, + .minimum_zig_version = "0.16.0", + .dependencies = .{}, + .paths = .{""}, +} diff --git a/deps/libgc/build.zig.zon b/deps/libgc/build.zig.zon new file mode 100644 index 000000000..0e2025981 --- /dev/null +++ b/deps/libgc/build.zig.zon @@ -0,0 +1,8 @@ +.{ + .name = .libgc, + .version = "0.0.0", + .fingerprint = 0x699a07146c24bd98, + .minimum_zig_version = "0.16.0", + .dependencies = .{}, + .paths = .{""}, +} diff --git a/deps/libnetstring/build.zig.zon b/deps/libnetstring/build.zig.zon new file mode 100644 index 000000000..66fb922ff --- /dev/null +++ b/deps/libnetstring/build.zig.zon @@ -0,0 +1,8 @@ +.{ + .name = .libnetstring, + .version = "0.0.0", + .fingerprint = 0x93f788cd241fc695, + .minimum_zig_version = "0.16.0", + .dependencies = .{}, + .paths = .{""}, +} diff --git a/deps/libprotobuf_c/build.zig.zon b/deps/libprotobuf_c/build.zig.zon new file mode 100644 index 000000000..7b1405cce --- /dev/null +++ b/deps/libprotobuf_c/build.zig.zon @@ -0,0 +1,8 @@ +.{ + .name = .libprotobuf_c, + .version = "0.0.0", + .fingerprint = 0xfcfd7b442ded66a4, + .minimum_zig_version = "0.16.0", + .dependencies = .{}, + .paths = .{""}, +} diff --git a/deps/libsnappy_c/build.zig.zon b/deps/libsnappy_c/build.zig.zon new file mode 100644 index 000000000..34f155188 --- /dev/null +++ b/deps/libsnappy_c/build.zig.zon @@ -0,0 +1,8 @@ +.{ + .name = .libsnappy, + .version = "0.0.0", + .fingerprint = 0xf5ea7e597512b0cd, + .minimum_zig_version = "0.16.0", + .dependencies = .{}, + .paths = .{""}, +} diff --git a/deps/libutf8proc/build.zig.zon b/deps/libutf8proc/build.zig.zon new file mode 100644 index 000000000..dc617fee9 --- /dev/null +++ b/deps/libutf8proc/build.zig.zon @@ -0,0 +1,8 @@ +.{ + .name = .libutf8proc, + .version = "0.0.0", + .fingerprint = 0xd7d11c6f6da16d6b, + .minimum_zig_version = "0.16.0", + .dependencies = .{}, + .paths = .{""}, +} diff --git a/deps/libuuid/build.zig.zon b/deps/libuuid/build.zig.zon new file mode 100644 index 000000000..018aea66b --- /dev/null +++ b/deps/libuuid/build.zig.zon @@ -0,0 +1,8 @@ +.{ + .name = .libuuid, + .version = "0.0.0", + .fingerprint = 0xed480ceafcee79e1, + .minimum_zig_version = "0.16.0", + .dependencies = .{}, + .paths = .{""}, +} diff --git a/deps/libuv/build.zig.zon b/deps/libuv/build.zig.zon new file mode 100644 index 000000000..a42920e07 --- /dev/null +++ b/deps/libuv/build.zig.zon @@ -0,0 +1,8 @@ +.{ + .name = .libuv, + .version = "0.0.0", + .fingerprint = 0x7cb3932c5d4cacd6, + .minimum_zig_version = "0.16.0", + .dependencies = .{}, + .paths = .{""}, +} diff --git a/deps/libxml2/build.zig.zon b/deps/libxml2/build.zig.zon new file mode 100644 index 000000000..938c60c00 --- /dev/null +++ b/deps/libxml2/build.zig.zon @@ -0,0 +1,8 @@ +.{ + .name = .libxml2, + .version = "0.0.0", + .fingerprint = 0xf268267bf50b038e, + .minimum_zig_version = "0.16.0", + .dependencies = .{}, + .paths = .{""}, +} diff --git a/deps/libyyjson/build.zig.zon b/deps/libyyjson/build.zig.zon new file mode 100644 index 000000000..4710d4a81 --- /dev/null +++ b/deps/libyyjson/build.zig.zon @@ -0,0 +1,8 @@ +.{ + .name = .libyyjson, + .version = "0.0.0", + .fingerprint = 0x1d7fa4acd6e9645e, + .minimum_zig_version = "0.16.0", + .dependencies = .{}, + .paths = .{""}, +} diff --git a/deps/mbedtls/build.zig.zon b/deps/mbedtls/build.zig.zon new file mode 100644 index 000000000..372994cde --- /dev/null +++ b/deps/mbedtls/build.zig.zon @@ -0,0 +1,8 @@ +.{ + .name = .libmbedtls, + .version = "0.0.0", + .fingerprint = 0x5328c30da4ecbb1f, + .minimum_zig_version = "0.16.0", + .dependencies = .{}, + .paths = .{""}, +} diff --git a/deps/pcre2/build.zig.zon b/deps/pcre2/build.zig.zon new file mode 100644 index 000000000..5a713fa2b --- /dev/null +++ b/deps/pcre2/build.zig.zon @@ -0,0 +1,8 @@ +.{ + .name = .libpcre2, + .version = "0.0.0", + .fingerprint = 0xee1d0784457618a7, + .minimum_zig_version = "0.16.0", + .dependencies = .{}, + .paths = .{""}, +} From cdef219274a95fa8879f4275fd3f2549c6bd4f51 Mon Sep 17 00:00:00 2001 From: Kristian Larsson Date: Tue, 2 Jun 2026 19:22:14 +0200 Subject: [PATCH 3/3] Add Windows dist cross-linux CI smoke --- .github/workflows/test.yml | 92 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 75bcf3c6e..3b5c07e94 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -358,6 +358,98 @@ jobs: ${{ github.workspace }}/test + test-windows-dist-cross-linux: + needs: build-version + runs-on: windows-latest + env: + BUILD_TIME: ${{ needs.build-version.outputs.build_time }} + steps: + - name: "Show platform and environment" + shell: pwsh + run: | + systeminfo + Get-ChildItem Env: | Sort-Object Name + - name: "Set BUILD_RELEASE when we are building for a version tag" + if: startsWith(github.ref, 'refs/tags/v') + shell: pwsh + run: | + "BUILD_RELEASE=1" >> $env:GITHUB_ENV + - name: "Check out repository code" + uses: actions/checkout@v6 + - name: "Cache Haskell deps" + uses: actions/cache@v5 + with: + path: | + ~\AppData\Roaming\stack + ~\AppData\Local\Programs\stack + key: stack-windows-x86_64-${{ hashFiles('compiler/stack.yaml', 'compiler/stack.yaml.lock') }} + restore-keys: | + stack-windows-x86_64- + - name: "Install Stack" + shell: pwsh + run: | + choco install haskell-stack -y --no-progress + stack --version + - name: "Install MSYS2 build prerequisites" + uses: msys2/setup-msys2@v2 + with: + update: true + path-type: inherit + install: >- + coreutils + curl + findutils + grep + make + sed + tar + unzip + - name: "Build Acton distribution on Windows" + shell: msys2 {0} + run: | + set -euo pipefail + workspace="$(cygpath -u "${GITHUB_WORKSPACE}")" + make -C "${workspace}" clean + make -C "${workspace}" BUILD_RELEASE="${BUILD_RELEASE:-}" BUILD_TIME="${BUILD_TIME}" + ls -l "${workspace}/dist/bin" + "${workspace}/dist/bin/acton.exe" --help >/dev/null + - name: "Compile helloworld to the default Linux target" + shell: msys2 {0} + run: | + set -euo pipefail + cd "$(cygpath -u "${GITHUB_WORKSPACE}")" + rm -rf tmp-ci-helloworld examples/helloworld + ./dist/bin/acton.exe --tempdir tmp-ci-helloworld examples/helloworld.act + test -s examples/helloworld + ls -l examples/helloworld + - name: "Upload Windows-built Linux ELF" + uses: actions/upload-artifact@v7 + with: + name: windows-built-linux-helloworld + path: ${{ github.workspace }}/examples/helloworld + if-no-files-found: error + + + run-windows-built-linux-elf: + needs: test-windows-dist-cross-linux + runs-on: ubuntu-latest + steps: + - name: "Download Windows-built Linux ELF" + uses: actions/download-artifact@v8 + with: + name: windows-built-linux-helloworld + path: windows-built-linux-helloworld + - name: "Run Windows-built Linux ELF" + run: | + set -euo pipefail + ls -l windows-built-linux-helloworld + file windows-built-linux-helloworld/helloworld + readelf -W -l windows-built-linux-helloworld/helloworld | sed -n '1,40p' + chmod +x windows-built-linux-helloworld/helloworld + windows-built-linux-helloworld/helloworld | tee helloworld.out + grep -Fx "Hello, world!" helloworld.out + + build-debs: needs: build-version strategy: