Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ miso build # public/index.js (target: browser)
| `miso lock` | write `miso.lock`: the dependencies of `miso.yml` and the packages they need; lock what is not locked yet, keep what is |
| `miso update` | a new package list for mcabal, and `miso.lock` again at its versions (git dependencies at their current commits); then install |
| `miso vendor` | lock as `miso lock` does, and copy the source of each locked dependency to `vendor/NAME`; builds then install from there, and fail rather than download |
| `miso cabal` | print the `.cabal` file of the project's library: what another project that depends on it builds it with |
| `miso doctor` | check the installation and the tools miso needs, and say how to fix what is missing |
| `miso upgrade [VERSION]` | install the latest release (or `VERSION`) in place |
| `miso uninstall [--yes]` | remove miso: `$MISO_HOME` (packages, caches) and the tool where `install.sh` put it. It shows what it removes and asks first; it lists the lines of your shell files that add miso to `PATH` (it does not edit them) |
Expand Down Expand Up @@ -132,6 +133,14 @@ executables:
- The top-level `main`, `source-dirs` and `target` (and the fields below)
cannot be used with `executables:`. A project with just `library:` is a library: `miso build`
checks it, and there is nothing to run.
- Another project can depend on a project with a library, from `git:` or a
`path:`, as on any package. miso builds it from a copy (in
`$MISO_HOME/src/cabalized`) with the `.cabal` file of its library, which
`miso cabal` prints: its `name`, `version`, modules, dependencies and the
fields below. The checkout itself is not changed. The `name:` of the
dependency is the project's. Its dependencies are by name (from Hackage,
or the tool's); one it takes from `git:` or a `path:` has to be listed in
the project that uses it instead.

Tests are programs that run on this machine, and pass when they exit with 0
(as Cabal's `exitcode-stdio` tests):
Expand Down
18 changes: 18 additions & 0 deletions scripts/smoke-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -157,4 +157,22 @@ grep -q 'Installing miso .*(built already, ' miso.log || { echo "smoke test: mis
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; }
# a miso project with a library: section, as a path: dependency of another
# project (built from the .cabal file miso cabal makes of it), with a
# package of its own; and a change to it, picked up
cd "$tmp"
mkdir -p greet/src
printf 'name: greet\nversion: 0.3.0\ndependencies: [split]\nlibrary:\n source-dirs: [src]\n' > greet/miso.yml
printf 'module Greet (greet) where\nimport Data.List.Split (splitOn)\ngreet :: String\ngreet = concat (splitOn "," "he,llo")\n' > greet/src/Greet.hs
(cd greet && miso cabal) | grep -q '^ exposed-modules: Greet$' || { echo "smoke test: miso cabal did not list the library's modules" >&2; (cd greet && miso cabal) >&2; exit 1; }
miso new usegreet > /dev/null
cd usegreet
printf 'name: usegreet\nversion: 0.1.0\nmain: Main\nsource-dirs: [app]\ntarget: native\ndependencies:\n - name: greet\n path: ../greet\n' > miso.yml
printf 'import Greet (greet)\nmain :: IO ()\nmain = putStrLn greet\n' > app/Main.hs
miso run > greet.log 2>&1 || { echo "smoke test: a miso project as a path dependency did not build" >&2; cat greet.log >&2; exit 1; }
[ "$(tail -1 greet.log | tr -d '\r')" = hello ] || { echo "smoke test: expected hello from the greet library" >&2; cat greet.log >&2; exit 1; }
sed -e 's/he,llo/hi,there/' ../greet/src/Greet.hs > ../greet/src/Greet.hs.new && mv ../greet/src/Greet.hs.new ../greet/src/Greet.hs
miso run > greet2.log 2>&1 || { echo "smoke test: the changed greet library did not build" >&2; cat greet2.log >&2; exit 1; }
[ "$(tail -1 greet2.log | tr -d '\r')" = hithere ] || { echo "smoke test: the change to the greet library was not picked up" >&2; cat greet2.log >&2; exit 1; }
[ ! -e ../greet/greet.cabal ] || { echo "smoke test: miso wrote a .cabal file into the dependency's folder" >&2; exit 1; }
echo "smoke test: ok"
17 changes: 14 additions & 3 deletions src/Miso/CLI/Build.hs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module Miso.CLI.Build
, libraryArgs
, programArgs
, nativeFiles
, libraryFiles
, modulesIn
) where

Expand All @@ -30,11 +31,21 @@ nativeFiles cfg exe = do
let lib = fmap libNative (cfgLibrary cfg)
own = exeNative exe
dflt = maybe True (const False) lib -- the program has the defaults
(lc, lj, li) <- maybe (pure ([], [], [])) (files True) lib
(oc, oj, oi) <- files dflt own
(lc, lj, li) <- maybe (pure ([], [], [])) (nativeFilesOf True) lib
(oc, oj, oi) <- nativeFilesOf dflt own
pure (lc ++ oc, lj ++ oj, li ++ oi)

-- | The C, JavaScript and include files of the library (with the defaults
-- cbits/*.c, jsbits/*.js and include/, where they are there).
libraryFiles :: Library -> IO ([FilePath], [FilePath], [FilePath])
libraryFiles lib = nativeFilesOf True (libNative lib)

-- | The C and JavaScript files and include dirs of one component; with dflt,
-- the defaults apply to what miso.yml does not list.
nativeFilesOf :: Bool -> Native -> IO ([FilePath], [FilePath], [FilePath])
nativeFilesOf dflt n = files
where
files dflt n = do
files = do
c <- globs (pick dflt ["cbits/*.c"] (cSources n))
j <- globs (pick dflt ["jsbits/*.js"] (jsSources n))
i <- existing (pick dflt ["include"] (includeDirs n))
Expand Down
117 changes: 117 additions & 0 deletions src/Miso/CLI/Cabalize.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
-- | A miso project with a library, as a dependency of another project.
--
-- mcabal builds packages from a .cabal file. A dependency (from git:, or a
-- path:) that is a miso project has a miso.yml and a library: section
-- instead: 'cabalized' makes a copy of it with the .cabal file of its
-- library ('cabalFile', which miso cabal prints), and the copy is what is
-- read for its dependencies and built. The checkout itself is not changed.
module Miso.CLI.Cabalize
( cabalFile
, cabalized
) where

import Control.Monad (unless, when)
import Data.List (intercalate, isPrefixOf, nub)
import System.Directory (createDirectoryIfMissing, doesFileExist, getCurrentDirectory, listDirectory)

import Miso.CLI.Build (libraryFiles, modulesIn)
import Miso.CLI.Color
import Miso.CLI.Config
import Miso.CLI.Util

-- | The .cabal file of the library of the miso.yml here (miso cabal).
cabalFile :: Config -> IO String
cabalFile cfg = do
lib <- maybe (failWith (cfgName cfg ++ " has no library: section in " ++ configFile
++ ", so it has nothing another project can import (a library: section lists its modules' source-dirs)")) pure
(cfgLibrary cfg)
-- What it needs, by name: the packages of the top-level list and of the
-- library's. One from git: or a path: of its own would need the project
-- that uses this one to fetch it too, which it does not do yet.
let deps = nub (cfgDeps cfg ++ libDeps lib)
case [ d | d <- deps, isGit d || isLocal d ] of
[] -> pure ()
ds -> failWith (cfgName cfg ++ " needs " ++ intercalate ", " (map depName ds)
++ " from git: or a path:, which a project that uses " ++ cfgName cfg
++ " cannot fetch for it yet; list them in that project's " ++ configFile ++ " instead")
mods <- maybe (concat <$> mapM modulesIn (libSourceDirs lib)) pure (libExposed lib)
when (null mods) $ failWith ("the library has no modules in " ++ unwords (libSourceDirs lib))
(cs, js, incs) <- libraryFiles lib
headers <- case incs of
d : _ -> filter (".h" `isSuffixOf'`) <$> listDirectory d
[] -> pure []
let n = libNative lib
field k vs = [ " " ++ k ++ ": " ++ intercalate ", " vs | not (null vs) ]
opts k vs = [ " " ++ k ++ ": " ++ unwords vs | not (null vs) ]
pure $ unlines $
[ "-- written by miso " ++ version ++ " from " ++ configFile ++ " (miso cabal)"
, "cabal-version: 2.2"
, "name: " ++ cfgName cfg
, "version: " ++ cfgVersion cfg
] ++ [ "synopsis: " ++ s | Just s <- [cfgSynopsis cfg] ]
++ [ "author: " ++ a | Just a <- [cfgAuthor cfg] ] ++
[ "build-type: Simple"
, ""
, "library"
, " default-language: Haskell2010"
] ++ field "hs-source-dirs" (libSourceDirs lib)
++ field "exposed-modules" mods
++ field "build-depends" ("base" : [ depName d ++ maybe "" (" == " ++) (depVersion d) | d <- deps ])
++ opts "mhs-options" (cfgMhsOptions cfg ++ libMhsOptions lib)
++ field "c-sources" cs
++ field "js-sources" js
++ field "include-dirs" incs
++ field "install-includes" headers
++ opts "cpp-options" (cppOptions n)
++ opts "cc-options" (ccOptions n)
++ opts "ld-options" (ldOptions n)
++ field "extra-libraries" (extraLibraries n)

-- | The directory to read and build a dependency from: dir itself when it
-- has a .cabal file (or nothing to make one from), otherwise a copy with the
-- .cabal file of its miso.yml, in $MISO_HOME/src/cabalized. The copy is
-- made again each time, as the source (a path:) may have changed. With
-- the name the dependency has in miso.yml, it is checked to be the
-- project's.
cabalized :: Maybe String -> FilePath -> IO FilePath
cabalized mname dir = do
es <- listDirectory dir
let hasCabal = any (".cabal" `isSuffixOf'`) es
hasYml <- doesFileExist (dir ++ "/" ++ configFile)
if hasCabal || not hasYml then pure dir else do
name <- maybe (either failWith (pure . cfgName) . parseConfig =<< readFile' (dir ++ "/" ++ configFile)) pure mname
home <- misoHome
here <- getCurrentDirectory
let abs' = if "/" `isPrefixOf` dir then dir else here ++ "/" ++ dir
key <- maybe "" (takeWhile (/= ' ')) <$> readCommand ("printf '%s' " ++ quote abs' ++ " | cksum")
let base = home ++ "/src/cabalized"
stage = base ++ "/" ++ name ++ "-" ++ key
tmp = stage ++ ".tmp"
createDirectoryIfMissing True base
-- its files, without what builds and tools keep there
run ("rm -rf " ++ quote tmp ++ " && mkdir " ++ quote tmp
++ " && cd " ++ quote dir
++ " && tar -cf - --exclude=./.git --exclude=./.miso --exclude=./dist --exclude=./dist-mcabal"
++ " --exclude=./public --exclude=./vendor --exclude=./.mhscache . | tar -xf - -C " ++ quote tmp)
me <- self
(ok, out) <- captureIn tmp (quote me ++ " cabal")
unless ok $ do
_ <- tryRun ("rm -rf " ++ quote tmp)
failWith ("dependency " ++ name ++ " (" ++ dir ++ ") is a miso project, but miso cannot use it as a package:\n" ++ out)
pkg <- either failWith (pure . cfgName) . parseConfig =<< readFile' (tmp ++ "/" ++ configFile)
when (pkg /= name) $ do
_ <- tryRun ("rm -rf " ++ quote tmp)
failWith ("dependency " ++ name ++ " is " ++ dir ++ ", whose " ++ configFile ++ " names it " ++ pkg
++ "; use name: " ++ pkg ++ " for it")
writeFile (tmp ++ "/" ++ name ++ ".cabal") out
run ("rm -rf " ++ quote stage ++ " && mv " ++ quote tmp ++ " " ++ quote stage)
pure stage
where
-- run a command in a directory: did it work, and what it printed (stdout)
captureIn d c = do
out <- readCommand ("cd " ++ quote d ++ " && MISO_PROJECT= " ++ c)
case out of
Just s -> pure (True, s)
Nothing -> do
err <- maybe "" id <$> readCommand ("cd " ++ quote d ++ " && MISO_PROJECT= " ++ c ++ " 2>&1; true")
pure (False, err)
14 changes: 13 additions & 1 deletion src/Miso/CLI/Deps.hs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import System.Directory (createDirectoryIfMissing, doesDirectoryExist, doesFileE
import qualified MicroCabal.Main as MC
import MicroHs.Compile (mhsVersion)

import Miso.CLI.Cabalize (cabalized)
import Miso.CLI.Color
import Miso.CLI.Config
import Miso.CLI.Lock
Expand Down Expand Up @@ -332,7 +333,18 @@ unitKey planned toolIds u ds = do
-- | Where the source of a package is, fetched if needed: Nothing when mcabal
-- installs it by name (mcabal -r).
sourceOf :: Maybe [(String, (String, String))] -> Dep -> Maybe Locked -> IO (Maybe FilePath, Maybe String)
sourceOf vend d ml =
sourceOf vend d ml = do
(mdir, from) <- sourceOf' vend d ml
-- a miso project (miso.yml, no .cabal file): built from a copy with the
-- .cabal file of its library
case mdir of
Just dir -> do
dir' <- cabalized (Just (maybe (depName d) lockedName ml)) dir
pure (Just dir', from)
Nothing -> pure (Nothing, from)

sourceOf' :: Maybe [(String, (String, String))] -> Dep -> Maybe Locked -> IO (Maybe FilePath, Maybe String)
sourceOf' vend d ml =
case (vend, ml) of
(Just vs, _) | not (isLocal d) -> do
dir <- vendoredDir vs ml d
Expand Down
3 changes: 3 additions & 0 deletions src/Miso/CLI/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import qualified MicroHs.Main as MHS
import MicroHs.Compile (mhsVersion)

import Miso.CLI.Build
import Miso.CLI.Cabalize (cabalFile)
import Miso.CLI.Color
import Miso.CLI.Config
import Miso.CLI.Deps
Expand Down Expand Up @@ -56,6 +57,7 @@ main' = do
"new" : _ -> failWith "usage: miso new NAME [--template T] (miso templates lists the templates)"
"init" : as -> initProject as
["templates"] -> listTemplates
["cabal"] -> withConfig (\cfg -> cabalFile cfg >>= putStr)
"build" : as -> withConfig (build ("--dev" `elem` as) (filter (/= "--dev") as))
"run" : as -> withConfig (runProgram as)
"test" : as -> withConfig (test as)
Expand Down Expand Up @@ -117,6 +119,7 @@ usage = do
, cmd "lock" "write miso.lock, keeping the versions locked already"
, cmd "update" "lock Hackage and git dependencies in miso.lock, and install them"
, cmd "vendor" "copy the locked dependencies' sources to vendor/, to build from"
, cmd "cabal" "print the .cabal file of the library (other projects build it from that)"
]
ts <- sequence
[ cmd "doctor" "check the installation and the tools miso needs"
Expand Down
5 changes: 4 additions & 1 deletion src/Miso/CLI/Resolve.hs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import qualified MicroCabal.StackageList as MC

import MicroHs.Compile (mhsVersion)

import Miso.CLI.Cabalize (cabalized)
import Miso.CLI.Color
import Miso.CLI.Config
import Miso.CLI.Lock
Expand Down Expand Up @@ -142,7 +143,9 @@ importsMiso cfg = do
-- and the cabal flags given, with MicroHs's names, as mcabal reads them;
-- not the package's own (internal) libraries.
packageDeps :: MC.Env -> [(String, Bool)] -> String -> FilePath -> IO [String]
packageDeps env fl why dir = do
packageDeps env fl why dir0 = do
-- a miso project: its library, as the .cabal file miso makes of it
dir <- cabalized Nothing dir0
cabals <- filter (".cabal" `isSuffixOf'`) <$> listDirectory dir
f <- case cabals of
[f] -> pure f
Expand Down
Loading