From a1fc8eca667c049002294da9a0dc402ad72731f5 Mon Sep 17 00:00:00 2001 From: "code@dmj.io" Date: Tue, 20 Oct 2026 20:05:30 -0500 Subject: [PATCH] miso is a dependency, locked like the others: not on every project's path The release took miso among the tool's packages, so every project used the miso of the tool it was built with, which miso.lock did not record: a new release of the tool changed the miso of every project. miso is now a dependency (miso new writes dependencies: [miso]), locked as a git dependency at a commit of dmjio/miso. - miso, with no source of its own, is the miso of the release: the commit of flake.lock it was built from (mhs-VERSION/miso-default, with its version and flags), locked at that commit. miso install copies the package the release built (mhs-VERSION/miso-prebuilt) when that is the one locked and built against the tool's packages, and builds it from the source the release ships otherwise (without a download). A project keeps the commit of its miso.lock with a new release; miso update moves it to the new release's. scripts/prebuilt-miso.sh takes miso out of the tool's package database (build-release.sh and flake.nix run it). A project whose code imports Miso without depending on miso gets the release's, with a warning; with a release from before, miso is among the tool's packages as it was. --- README.md | 34 +++++++++++++---- flake.nix | 7 +++- scripts/build-release.sh | 3 ++ scripts/prebuilt-miso.sh | 33 +++++++++++++++++ scripts/smoke-test.sh | 15 +++++++- src/Miso/CLI/Config.hs | 4 +- src/Miso/CLI/Deps.hs | 42 +++++++++++++++------ src/Miso/CLI/Doctor.hs | 6 ++- src/Miso/CLI/Lock.hs | 2 +- src/Miso/CLI/Resolve.hs | 47 ++++++++++++++++++++++-- src/Miso/CLI/Store.hs | 77 ++++++++++++++++++++++++++++++++++++++- src/Miso/CLI/Templates.hs | 5 ++- 12 files changed, 243 insertions(+), 32 deletions(-) create mode 100755 scripts/prebuilt-miso.sh diff --git a/README.md b/README.md index d705b7f..c623eda 100644 --- a/README.md +++ b/README.md @@ -180,8 +180,20 @@ first error, so after fixing one, the next shows on the next save. Some packages come with the tool, in its package database (`~/.miso/mhs-VERSION`): the compiler's (`base`, `ghc-compat`, `array-mhs`, -`template-haskell`), and the libraries `containers`, `mtl`, `transformers` -and `miso`. The packages a project installs go in the project, in +`template-haskell`), and the libraries `containers`, `mtl` and +`transformers`. + +miso is not one of them: a project depends on it (`miso new` writes +`dependencies: [miso]`), and locks it like any other package. `- miso`, with +no source of its own, is the miso of the tool's release: the commit of +dmjio/miso it was built from (`~/.miso/mhs-VERSION/miso-default`), locked as +a git dependency at that commit, and installed as it came with the release +(`~/.miso/mhs-VERSION/miso-prebuilt`), not built. A new release of the tool +does not change the miso of a project that has it in `miso.lock`; `miso +update` moves it to the new release's. Another miso is a dependency with +its own source (`git:`, `path:`, or a `version:` from Hackage). A project +whose code imports `Miso` but does not depend on it (from before) gets the +release's, with a warning. The packages a project installs go in the project, in `.miso/` (next to `miso.yml`; `miso new` puts it in `.gitignore`, and `miso clean` removes it). mhs looks for packages in the project's `.miso/` first, then in the tool's database, and nowhere else: what one @@ -209,10 +221,18 @@ it; `miso list` shows which: ```yaml dependencies: - - name: transformers # instead of the tool's 0.6.1.2: mtl and miso, - version: 0.6.1.1 # which need it, are built again against it - - name: miso # miso from a checkout (with the flags the tool's - path: ../miso # is built with, in miso-sources/miso-*.flags) + - miso # built again against transformers 0.6.1.1 + - name: transformers # instead of the tool's 0.6.1.2: mtl, which needs + version: 0.6.1.1 # it, is built again against it +``` + +miso from a checkout is a `path:` dependency, with the flags the release +builds it with (in `~/.miso/mhs-VERSION/miso-default`): + +```yaml +dependencies: + - name: miso + path: ../miso flags: -template-haskell -aeson -native -production -ssr -benchmark -text ``` @@ -235,7 +255,7 @@ version of the package there (from its `miso.yml`, or its `.cabal` file): # Local (path:) dependencies are not locked. miso: 0.1.0 mhs: 0.16.10.0 -bundled: containers-0.8 miso-1.14.0.0 mtl-2.3.1 transformers-0.6.1.2 +bundled: containers-0.8 mtl-2.3.1 transformers-0.6.1.2 packages: - name: split hackage: 0.2.5.1 diff --git a/flake.nix b/flake.nix index ae1dc0d..4d48ef8 100644 --- a/flake.nix +++ b/flake.nix @@ -48,8 +48,6 @@ cp build/miso build/cpphs $out/bin/ cp -r ${pkgs.miso-mhs}/lib/mcabal/mhs-${mhsVersion} $out/mhs-${mhsVersion} chmod -R u+w $out/mhs-${mhsVersion} - # what comes with the tool (miso list tells these from what users install) - (cd $out/mhs-${mhsVersion}/packages && ls *.pkg) > $out/mhs-${mhsVersion}/miso-bundled # the sources of its libraries, as miso-mhs built them, for # projects that replace one of them (scripts/ship-source.sh) db=$out/mhs-${mhsVersion} @@ -60,6 +58,11 @@ sh scripts/ship-source.sh $db mtl mtl sh scripts/ship-source.sh $db containers ${misoSrc.mhs-containers}/containers sh scripts/ship-source.sh $db miso ${misoSrc.miso} "-template-haskell -aeson -native -production -ssr -benchmark -text" + # miso is not on every project's package path: a project depends + # on it, at the commit of flake.lock, and installs this prebuilt one + sh scripts/prebuilt-miso.sh $db https://github.com/dmjio/miso ${miso.rev} "-template-haskell -aeson -native -production -ssr -benchmark -text" + # what comes with the tool (miso list tells these from what users install) + (cd $db/packages && ls *.pkg) > $db/miso-bundled runHook postInstall ''; }; diff --git a/scripts/build-release.sh b/scripts/build-release.sh index 08baed6..f08a61a 100755 --- a/scripts/build-release.sh +++ b/scripts/build-release.sh @@ -178,6 +178,9 @@ scripts/ship-source.sh "$db" transformers "$work/src/mhs-transformers" scripts/ship-source.sh "$db" mtl "$work/src/mhs-mtl" scripts/ship-source.sh "$db" containers "$work/src/mhs-containers/containers" scripts/ship-source.sh "$db" miso "$work/src/miso" "$misoflags" +# miso is not on every project's package path: a project depends on it, at +# the commit of flake.lock, and installs this prebuilt one +scripts/prebuilt-miso.sh "$db" https://github.com/dmjio/miso "$misorev" "$misoflags" # 3. The miso tool say "building miso" diff --git a/scripts/prebuilt-miso.sh b/scripts/prebuilt-miso.sh new file mode 100755 index 0000000..7e9d582 --- /dev/null +++ b/scripts/prebuilt-miso.sh @@ -0,0 +1,33 @@ +#!/bin/sh +# Take miso out of the tool's package database, where every project would +# use it, and keep it next to it, prebuilt: a project depends on miso (in +# its miso.yml) and locks it like any other package, at a commit of +# dmjio/miso, and miso install copies this one when it is the one locked. +# +# mhs-VERSION/miso-default the miso of this release: its repository, +# commit, version, and cabal flags +# mhs-VERSION/miso-prebuilt/ the package, as built from them (its .pkg, +# data directory, and module files) +# +# scripts/build-release.sh and flake.nix run it, after installing miso. +# Usage: scripts/prebuilt-miso.sh DB REPOSITORY COMMIT FLAGS +set -eu +db=$1 repo=$2 commit=$3 flags=$4 +pkg=$(cd "$db/packages" && ls | grep -E '^miso-[0-9.]+\.pkg$') +[ -n "$pkg" ] || { echo "prebuilt-miso: no miso package in $db/packages" >&2; exit 1; } +nv=${pkg%.pkg} +version=${nv#miso-} +out=$db/miso-prebuilt +rm -rf "$out" +mkdir -p "$out/packages" +# the module files that name the package (A/B.txt for module A.B) +(cd "$db" && grep -rlxF --include='*.txt' --exclude-dir=packages "$pkg" .) > "$db/.miso-modules" +(cd "$db" && tar -cf - -T .miso-modules) | (cd "$out" && tar -xf -) +(cd "$db" && xargs rm -f < .miso-modules) +# and the directories of those modules that are empty now +find "$db" -depth -type d -empty ! -path "$db/packages*" ! -path "$db/miso-prebuilt*" -exec rmdir {} \; +rm -f "$db/.miso-modules" +mv "$db/packages/$pkg" "$out/packages/" +if [ -d "$db/packages/$nv" ]; then mv "$db/packages/$nv" "$out/packages/"; fi +chmod -R u+w "$out" +printf 'repository: %s\ncommit: %s\nversion: %s\nflags: %s\n' "$repo" "$commit" "$version" "$flags" > "$db/miso-default" diff --git a/scripts/smoke-test.sh b/scripts/smoke-test.sh index ec64690..52ac70d 100755 --- a/scripts/smoke-test.sh +++ b/scripts/smoke-test.sh @@ -11,7 +11,7 @@ miso new hello cd hello # data-default-class needs data-default, which does not come with miso: # miso installs it too (by name, then locked, then from vendor/) -sed -e 's/^target: browser/target: native/' -e 's/^dependencies: \[\]/dependencies: [split, data-default-class]/' miso.yml > miso.yml.new +sed -e 's/^target: browser/target: native/' -e 's/^dependencies: \[miso\]/dependencies: [split, data-default-class]/' miso.yml > miso.yml.new mv miso.yml.new miso.yml cat > app/Main.hs <<'HS' {-# LANGUAGE CPP #-} @@ -114,7 +114,7 @@ case "$page" in *served*) ;; *) echo "smoke test: miso serve did not serve the p cd "$tmp" miso new hello2 > /dev/null cd hello2 -sed -e 's/^target: browser/target: native/' -e 's/^dependencies: \[\]/dependencies: [split]/' miso.yml > miso.yml.new +sed -e 's/^target: browser/target: native/' -e 's/^dependencies: \[miso\]/dependencies: [split]/' miso.yml > miso.yml.new mv miso.yml.new miso.yml printf 'import Data.List.Split (splitOn)\nmain :: IO ()\nmain = putStrLn (concat (splitOn "," "hel,lo"))\n' > app/Main.hs miso run > store.log 2>&1 || { echo "smoke test: miso run failed in a second project" >&2; cat store.log >&2; exit 1; } @@ -146,4 +146,15 @@ sed -e 's/name: transformers/name: base/' -e 's/version: 0.6.1.1/version: 1.0/' mv miso.yml.new miso.yml if miso install > base.log 2>&1; then echo "smoke test: replacing base did not fail" >&2; exit 1; fi grep -q 'base comes with miso as part of its compiler' base.log || { echo "smoke test: no error for replacing base" >&2; cat base.log >&2; exit 1; } +# a new project depends on miso, the miso of the release: installed as it +# came with the tool (not built), and locked at its commit +cd "$tmp" +miso new hello4 > /dev/null +cd hello4 +grep -q '^dependencies: \[miso\]' miso.yml || { echo "smoke test: a new project does not depend on miso" >&2; cat miso.yml >&2; exit 1; } +miso install > miso.log 2>&1 || { echo "smoke test: miso install failed in a new project" >&2; cat miso.log >&2; exit 1; } +grep -q 'Installing miso .*(built already, ' miso.log || { echo "smoke test: miso was built, not installed as it came" >&2; cat miso.log >&2; exit 1; } +ls .miso/mcabal/mhs-*/packages/miso-*.pkg >/dev/null || { echo "smoke test: miso is not in .miso/" >&2; exit 1; } +miso lock > /dev/null +grep -q '^ git: https://github.com/dmjio/miso$' miso.lock || { echo "smoke test: miso lock did not lock miso" >&2; cat miso.lock >&2; exit 1; } echo "smoke test: ok" diff --git a/src/Miso/CLI/Config.hs b/src/Miso/CLI/Config.hs index 05749a3..c0335e2 100644 --- a/src/Miso/CLI/Config.hs +++ b/src/Miso/CLI/Config.hs @@ -46,7 +46,9 @@ -- > source-dirs: [test] # default [test] -- > dependencies: [split] -- --- miso itself, and base, containers, mtl, ... come installed with the tool. +-- base, containers, mtl, ... come installed with the tool. miso is a +-- dependency like the others: - miso is the miso of the tool's release, +-- locked at its commit (Miso.CLI.Store). -- A dependency on one of those with a source (git:, path:, or a version: -- that is not the tool's) is installed in the project instead, and the -- tool's packages that need it are built again against it (Miso.CLI.Deps). diff --git a/src/Miso/CLI/Deps.hs b/src/Miso/CLI/Deps.hs index bcb4ad5..03f9c01 100644 --- a/src/Miso/CLI/Deps.hs +++ b/src/Miso/CLI/Deps.hs @@ -82,7 +82,8 @@ data Unit = Unit -- a local directory, nor something built against one) is kept in the store -- (Miso.CLI.Store), and copied from there when another project needs it. installDeps :: Config -> IO () -installDeps cfg = do +installDeps cfg0 = do + cfg <- withMiso cfg0 setupEnv root <- projectRoot >>= maybe (failWith ("no " ++ configFile ++ " here")) pure let db = projectDb root @@ -231,10 +232,11 @@ installLocked _ db _ tool replaced items _ = do done0 <- readStamp db let done = [ k | k <- done0, not (any (\n -> (n ++ "-") `isPrefixOf` k) dropped) ] -- (a local package is installed again when its files change) - let todo = [ (u, k) | ((u, _), (_, k)) <- zip ordered ids, keyStamp k `notElem` done ] + let todo = [ (u, ds, k) | ((u, ds), (_, k)) <- zip ordered ids, keyStamp k `notElem` done ] + planned = map (nameOf . fst) ordered if null todo then step "dependencies are installed" - else forM_ todo $ \(u, k) -> do + else forM_ todo $ \(u, ds, k) -> do let d = uDep u ver = case (uLocked u, uTool u) of (Just l, _) -> lockedAt l @@ -242,11 +244,19 @@ installLocked _ db _ tool replaced items _ = do _ -> maybe "" (" " ++) (depVersion d) what = depName d ++ ver fromCache <- case k of - Stored sid _ -> do + Stored sid keyLines -> do maybe (pure ()) (\v -> removeOtherVersions db (nameOf u) v) (unitVersion u) got <- fromStore db sid when got $ info ("Installing " ++ what ++ " (built already, from the store)") - pure got + -- the release's miso, built against the tool's packages + pre <- case uLocked u of + Just (LockedGit "miso" repo _ c _) | not got, all (`notElem` planned) ds -> + fromPrebuilt db repo c (maybe "" id (depFlags d)) + _ -> pure False + when pre $ do + info ("Installing " ++ what ++ " (built already, as it came with this miso)") + toStore db sid (depName d ++ "-" ++ maybe "" id (unitVersion u) ++ ".pkg") keyLines + pure (got || pre) _ -> pure False unless fromCache $ do info $ case uTool u of @@ -335,7 +345,7 @@ sourceOf vend d ml = unless unpacked $ run ("tar -xzf " ++ quote tarball ++ " -C " ++ quote (home ++ "/src/hackage")) pure (Just dir, Nothing) (_, Just (LockedGit n repo _ c _)) -> do - dir <- gitCheckout n repo c + dir <- checkoutAt n repo c pure (Just dir, Nothing) (_, Nothing) -> case (depGit d, depPath d, depVersion d) of (_, Just dir, _) -> do @@ -547,7 +557,8 @@ listPackages = do -- versions, and dropping those no longer needed. miso update moves all of -- them to the versions of a new package list. lockDeps :: Config -> IO () -lockDeps cfg = do +lockDeps cfg0 = do + cfg <- withMiso cfg0 setupEnv old <- maybe [] id <$> readLock info ("Locking the dependencies in " ++ lockFile) @@ -563,7 +574,8 @@ lockDeps cfg = do -- | The libraries of the tool the project uses (those it does not -- replace), as name-version, for miso.lock. usedToolLibraries :: Config -> IO [String] -usedToolLibraries cfg = do +usedToolLibraries cfg0 = do + cfg <- withMiso cfg0 tool <- bundledPackages replaced <- filterM (isReplacement tool) (allDeps cfg) pure [ bName b ++ "-" ++ bVersion b | b <- tool, bSource b /= Nothing, bName b `notElem` map depName replaced ] @@ -572,7 +584,8 @@ usedToolLibraries cfg = do -- snapshot), and every package locked again at its version, git -- dependencies at the commit of their rev now; then install them. updateDeps :: Config -> IO () -updateDeps cfg = do +updateDeps cfg0 = do + cfg <- withMiso cfg0 setupEnv info "Updating the package list" withGlobalCabal (runSelf "mcabal update") @@ -647,7 +660,8 @@ vendoredDir vs ml d = do -- the packages they need. Local dependencies, and the packages that come -- with the tool or with MicroHs, are not (they are not locked). vendorDeps :: Config -> IO () -vendorDeps cfg = do +vendorDeps cfg0 = do + cfg <- withMiso cfg0 lockDeps cfg lock <- maybe [] id <$> readLock old <- maybe [] id <$> readVendor @@ -694,8 +708,12 @@ vendorDeps cfg = do run ("mv " ++ quote (tmp ++ "/" ++ n ++ "-" ++ v) ++ " " ++ quote dir) LockedGit n repo _ c _ -> do -- the files of the commit, without .git and what builds left there - src <- gitCheckout n repo c - run ("git -C " ++ quote src ++ " archive --format=tar " ++ quote c ++ " | tar -xf - -C " ++ quote tmp) + -- (the release's miso is its source, not a checkout) + src <- checkoutAt n repo c + isRepo <- doesDirectoryExist (src ++ "/.git") + if isRepo + then run ("git -C " ++ quote src ++ " archive --format=tar " ++ quote c ++ " | tar -xf - -C " ++ quote tmp) + else run ("cd " ++ quote src ++ " && tar -cf - --exclude=dist-mcabal . | tar -xf - -C " ++ quote tmp) run ("mv " ++ quote tmp ++ " " ++ quote dir) run ("rm -rf " ++ quote tmp) step ("+ " ++ dir ++ " " ++ describe l) diff --git a/src/Miso/CLI/Doctor.hs b/src/Miso/CLI/Doctor.hs index 63313e2..9c98e84 100644 --- a/src/Miso/CLI/Doctor.hs +++ b/src/Miso/CLI/Doctor.hs @@ -37,7 +37,11 @@ doctor = do pkgs <- if dbOk then listDirectory (db ++ "/packages") else pure [] let hasPkg p = any ((p ++ "-") `isPrefixOf`) pkgs check (hasPkg "base") "base" "the package database is damaged; reinstall: miso upgrade" - check (hasPkg "miso") "miso" "the package database is damaged; reinstall: miso upgrade" + -- miso: prebuilt next to the packages (miso-prebuilt), or among them (a + -- release from before projects depended on miso) + hasDefault <- doesFileExist (db ++ "/miso-default") + prebuilt <- if hasDefault then doesDirectoryExist (db ++ "/miso-prebuilt/packages") else pure False + check (prebuilt || hasPkg "miso") "miso" "the package database is damaged; reinstall: miso upgrade" cpphs <- doesFileExist (parent exe ++ "/cpphs" ++ exeSuffix) check cpphs "cpphs (for modules with CPP)" ("missing next to " ++ exe ++ "; reinstall: miso upgrade") mhome <- misoHome diff --git a/src/Miso/CLI/Lock.hs b/src/Miso/CLI/Lock.hs index 3373872..c5e9db2 100644 --- a/src/Miso/CLI/Lock.hs +++ b/src/Miso/CLI/Lock.hs @@ -6,7 +6,7 @@ -- > # Local (path:) dependencies are not locked. -- > miso: 0.1.0 -- > mhs: 0.16.10.0 --- > bundled: containers-0.8 miso-1.14.0.0 mtl-2.3.1 transformers-0.6.1.2 +-- > bundled: containers-0.8 mtl-2.3.1 transformers-0.6.1.2 -- > packages: -- > - name: split # from Hackage: the version, and the -- > hackage: 0.2.5.1 # sha256 of its tarball diff --git a/src/Miso/CLI/Resolve.hs b/src/Miso/CLI/Resolve.hs index e61404f..9a6e106 100644 --- a/src/Miso/CLI/Resolve.hs +++ b/src/Miso/CLI/Resolve.hs @@ -18,6 +18,7 @@ module Miso.CLI.Resolve , packageDeps , mcabalEnv , isReplacement + , withMiso ) where import Control.Exception (SomeException, evaluate, try) @@ -106,6 +107,36 @@ needsInstall env bundled n isOurs p = (n ++ "-") `isPrefixOf` p && ".pkg" `isSuffixOf'` p && all (`elem` "0123456789.") (drop (length n + 1) (take (length p - 4) p)) +-- | The project's dependencies, with miso as a package like the others: a +-- dependency on miso with no source of its own (- miso) is the release's +-- (Miso.CLI.Store), and a project whose code imports Miso but does not +-- depend on it (miso.yml from a release that had miso among its packages) +-- depends on it too, with a warning. With such a release, as it is. +withMiso :: Config -> IO Config +withMiso cfg = do + md <- misoDefault + case md of + Nothing -> pure cfg + Just m -> do + let has = any ((== "miso") . depName) (allDeps cfg) + imports <- if has then pure False else importsMiso cfg + when imports $ + warn ("the code imports Miso, but miso.yml has no dependency on miso; using the miso of this release " + ++ mdVersion m ++ " (miso add miso adds it)") + let plain d = depName d == "miso" && isHackage d && depVersion d == Nothing + fix ds = [ if plain d then defaultMisoDep m (depFlags d) else d | d <- ds ] + pure cfg { cfgDeps = fix (cfgDeps cfg) ++ [ defaultMisoDep m Nothing | imports ] + , cfgLibrary = fmap (\l -> l { libDeps = fix (libDeps l) }) (cfgLibrary cfg) + , cfgExecutables = [ e { exeDeps = fix (exeDeps e) } | e <- cfgExecutables cfg ] + , cfgTests = [ t { exeDeps = fix (exeDeps t) } | t <- cfgTests cfg ] } + +-- | Does the project's code import a module of miso? +importsMiso :: Config -> IO Bool +importsMiso cfg = do + dirs <- filterM doesDirectoryExist (allSourceDirs cfg) + if null dirs then pure False + else tryRun ("grep -rqE --include='*.hs' '^import +(qualified +)?Miso' " ++ unwords (map quote dirs)) + -- | The packages a package needs: the build-depends of its libraries and -- executables that are buildable, for this OS, architecture and compiler -- and the cabal flags given, with MicroHs's names, as mcabal reads them; @@ -152,9 +183,15 @@ resolveTree keep old cfg = do -- a package of the tool that miso.yml replaces is locked as any other let bundled = [ bName b | b <- tool, bName b `notElem` map depName replaced ] home <- misoHome + md <- misoDefault state <- newIORef ([], []) -- the packages seen, and the lock (reversed) let pn = patchedName env roots = allDeps cfg + -- the dependencies of miso.yml by that name; miso, when a package + -- needs it, is the release's + rootsFor n = case [ d | d <- roots, pn (depName d) == n ] of + [] | n == "miso", Just m <- md -> [defaultMisoDep m Nothing] + ds -> ds flagsOf d = maybe [] MC.decodeCabalFlags (depFlags d) emit l = modifyIORef state (\(seen, out) -> (seen, l : out)) -- why a package is needed: "(needed by fail, needed by transformers-compat, ...)" @@ -165,14 +202,18 @@ resolveTree keep old cfg = do (seen, _) <- readIORef state unless (n `elem` seen) $ do modifyIORef state (\(s, out) -> (n : s, out)) - case [ d | d <- roots, pn (depName d) == n ] of + case rootsFor n of d : _ | Just dir <- depPath d -> do ok <- doesDirectoryExist dir unless ok $ failWith ("dependency " ++ depName d ++ ": no directory " ++ dir) packageDeps env (flagsOf d) (why chain) dir >>= mapM_ (visit (n : chain)) d : _ | isGit d -> do - l <- maybe (resolveGit d) pure (if keep then lockedFor pn old d else Nothing) - dir <- gitCheckout (lkName l) (lkRepo l) (lkCommit l) + let resolve = case md of + -- the release's miso, at its commit + Just m | isDefaultMiso m d -> pure (LockedGit "miso" (mdRepo m) Nothing (mdCommit m) (Just (mdVersion m))) + _ -> resolveGit d + l <- maybe resolve pure (if keep then lockedFor pn old d else Nothing) + dir <- checkoutAt (lkName l) (lkRepo l) (lkCommit l) packageDeps env (flagsOf d) (why chain) dir >>= mapM_ (visit (n : chain)) emit l mroot -> do diff --git a/src/Miso/CLI/Store.hs b/src/Miso/CLI/Store.hs index c98c550..e92614a 100644 --- a/src/Miso/CLI/Store.hs +++ b/src/Miso/CLI/Store.hs @@ -29,6 +29,12 @@ module Miso.CLI.Store , pkgSums , removeOtherVersions , toolView + , MisoDefault (..) + , misoDefault + , isDefaultMiso + , defaultMisoDep + , checkoutAt + , fromPrebuilt ) where import Control.Monad (filterM, forM, forM_, unless, when) @@ -38,7 +44,8 @@ import System.Directory (createDirectoryIfMissing, doesDirectoryExist, doesFileE import MicroHs.Compile (mhsVersion) import Miso.CLI.Color -import Miso.CLI.Lock (sha256File) +import Miso.CLI.Config (Dep (..)) +import Miso.CLI.Lock (gitCheckout, sha256File) import Miso.CLI.Util -- | A package that comes with the tool. @@ -185,6 +192,74 @@ toolView root tool replaced = do -- a link (on Windows, MSYS2 copies) link from to = run ("ln -s " ++ quote from ++ " " ++ quote to) +----------------------------------------------------------------------------- +-- The miso of the release +-- +-- miso is not one of the tool's packages: a project depends on it, as on any +-- other, and locks it. A dependency on miso with no source of its own +-- (- miso) is the miso of the release: mhs-VERSION/miso-default has its +-- repository, commit, version and cabal flags, mhs-VERSION/miso-sources its +-- source, and mhs-VERSION/miso-prebuilt the package built from them +-- (scripts/prebuilt-miso.sh), which is installed instead of building it +-- again. A release without miso-default has miso among its packages. + +data MisoDefault = MisoDefault + { mdRepo :: String + , mdCommit :: String + , mdVersion :: String + , mdFlags :: String + } deriving (Show, Eq) + +misoDefault :: IO (Maybe MisoDefault) +misoDefault = do + db <- toolDb + let f = db ++ "/miso-default" + there <- doesFileExist f + if not there then pure Nothing else do + s <- readFile' f + let kvs = [ (k, dropWhile (== ' ') (drop 1 v)) | l <- lines s, let (k, v) = break (== ':') l ] + pure (MisoDefault <$> lookup "repository" kvs <*> lookup "commit" kvs <*> lookup "version" kvs + <*> Just (maybe "" id (lookup "flags" kvs))) + +-- | The dependency on the miso of the release: from its repository, with no +-- rev of its own (it is locked at the release's commit), and its flags. +defaultMisoDep :: MisoDefault -> Maybe String -> Dep +defaultMisoDep m fl = Dep "miso" (Just (mdRepo m)) Nothing Nothing (Just (maybe (mdFlags m) id fl)) Nothing + +isDefaultMiso :: MisoDefault -> Dep -> Bool +isDefaultMiso m d = depName d == "miso" && depGit d == Just (mdRepo m) && depRev d == Nothing + +-- | The source of a package at a commit of a repository: the release's +-- (miso at its commit, without a download), or a checkout. +checkoutAt :: String -> String -> String -> IO FilePath +checkoutAt n repo c = do + md <- misoDefault + db <- toolDb + case md of + Just m | n == "miso", repo == mdRepo m, c == mdCommit m -> do + let tarball = db ++ "/miso-sources/miso-" ++ mdVersion m ++ ".tar.gz" + has <- doesFileExist tarball + if has then bundledSourceDir (Bundled "miso" (mdVersion m) "" (Just tarball) (Just (mdFlags m))) + else gitCheckout n repo c + _ -> gitCheckout n repo c + +-- | Copy the release's prebuilt miso into a package database, if it is the +-- one: the release's commit and flags (and built against the tool's +-- packages, which the caller knows). +fromPrebuilt :: FilePath -> String -> String -> String -> IO Bool +fromPrebuilt db repo c fl = do + md <- misoDefault + tdb <- toolDb + let dir = tdb ++ "/miso-prebuilt" + there <- doesDirectoryExist (dir ++ "/packages") + case md of + Just m | there, repo == mdRepo m, c == mdCommit m, words fl == words (mdFlags m) -> do + createDirectoryIfMissing True db + run ("cp -R " ++ quote dir ++ "/. " ++ quote db ++ "/") + run ("chmod -R u+w " ++ quote db) + pure True + _ -> pure False + ----------------------------------------------------------------------------- -- The store diff --git a/src/Miso/CLI/Templates.hs b/src/Miso/CLI/Templates.hs index feb32dc..f3ad923 100644 --- a/src/Miso/CLI/Templates.hs +++ b/src/Miso/CLI/Templates.hs @@ -31,8 +31,9 @@ misoYml name = unlines , "source-dirs: [app]" , "# browser: WebAssembly (needs emcc), browser_js: JavaScript, native: an executable" , "target: browser" - , "# miso, containers, mtl, ... come with the miso tool; add other packages here" - , "dependencies: []" + , "# miso: the miso of this release of the tool, locked in miso.lock; containers," + , "# mtl, ... come with the tool; add other packages here" + , "dependencies: [miso]" ] counterHs :: String -> String