diff --git a/data/default.nix b/data/default.nix index 136cfaf..50cf00b 100644 --- a/data/default.nix +++ b/data/default.nix @@ -1,51 +1,69 @@ { nixpkgs ? , config ? {} }: - with (import nixpkgs config); - let mkDerivation = - { srcs ? ./elm-srcs.nix - , src + { elmsrcs ? ./elm-srcs.nix + , srcs + , srcdir ? "src" , name - , srcdir ? "./src" , targets ? [] , versionsDat ? ./versions.dat , outputJavaScript ? false }: - stdenv.mkDerivation { - inherit name src; + let sanitizePath = str: + let path = builtins.filter (p: p != "..") (lib.splitString "/" str); + in lib.concatStringsSep "/" (map (p: if p == "." then srcdir else p) path); + in stdenv.mkDerivation { + inherit name srcs; + sourceRoot = "."; buildInputs = [ elmPackages.elm ] ++ lib.optional outputJavaScript nodePackages_10_x.uglify-js; + patchPhase = let + elmjson = let json = (lib.importJSON ./elm.json) // { source-directories = map sanitizePath srcs; }; + in writeText "elm.json" (builtins.toJSON json); + in '' + echo "Generating elm.json" + cp \${elmjson} ./elm.json + cat elm.json + ''; + buildPhase = pkgs.elmPackages.fetchElmDeps { - elmPackages = import srcs; + elmPackages = import elmsrcs; inherit versionsDat; }; - installPhase = let - elmfile = module: "\${srcdir}/\${builtins.replaceStrings ["."] ["/"] module}.elm"; - extension = if outputJavaScript then "js" else "html"; + installPhase = + let extension = if outputJavaScript then "js" else "html"; in '' mkdir -p \$out/share/doc - \${lib.concatStrings (map (module: '' - echo "compiling \${elmfile module}" - elm make \${elmfile module} --output \$out/\${module}.\${extension} --docs \$out/share/doc/\${module}.json + \${lib.concatStrings (map (module: + let fullmodule = sanitizePath module; + modulename = sanitizePath (lib.removePrefix "./" module); + in '' + echo "compiling \${modulename}" + elm make \${fullmodule}.elm --output \$out/\${modulename}.\${extension} --docs \$out/share/doc/\${modulename}.json \${lib.optionalString outputJavaScript '' - echo "minifying \${elmfile module}" - uglifyjs $out/\${module}.\${extension} --compress 'pure_funcs="F2,F3,F4,F5,F6,F7,F8,F9,A2,A3,A4,A5,A6,A7,A8,A9",pure_getters,keep_fargs=false,unsafe_comps,unsafe' \\ - | uglifyjs --mangle --output=$out/\${module}.min.\${extension} + echo "minifying \${modulename}" + uglifyjs $out/\${modulename}.\${extension} --compress 'pure_funcs="F2,F3,F4,F5,F6,F7,F8,F9,A2,A3,A4,A5,A6,A7,A8,A9",pure_getters,keep_fargs=false,unsafe_comps,unsafe' \\ + | uglifyjs --mangle --output=$out/\${modulename}.min.\${extension} ''} '') targets)} ''; }; in mkDerivation { name = "${name}"; - srcs = ./elm-srcs.nix; - src = ./.; - targets = ["Main"]; + elmsrcs = ./elm-srcs.nix; + srcs = ${srcs}; + # Generated by elm2nix + # should be *name* of current directory is used to find sources srcdir = "${srcdir}"; + # Please configure this line + # Should be relative path as a string to modules you want to compile + # excluding `.elm` extension + targets = ["./Main"]; outputJavaScript = false; } diff --git a/elm2nix.cabal b/elm2nix.cabal index 23d2b36..3ee0294 100644 --- a/elm2nix.cabal +++ b/elm2nix.cabal @@ -45,6 +45,8 @@ library , text , transformers , unordered-containers + , vector + , directory exposed-modules: Elm2Nix Elm2Nix.FixedOutput @@ -72,14 +74,3 @@ executable elm2nix , here , ansi-wl-pprint default-language: Haskell2010 - -test-suite elm2nix-test - type: exitcode-stdio-1.0 - main-is: Spec.hs - hs-source-dirs: - test - ghc-options: -threaded -rtsopts -with-rtsopts=-N - build-depends: - base >= 4.7 && < 5 - , elm2nix - default-language: Haskell2010 diff --git a/scripts/tests.sh b/scripts/tests.sh index 26e49b4..232d7cb 100755 --- a/scripts/tests.sh +++ b/scripts/tests.sh @@ -27,3 +27,11 @@ pushd $MYTMPDIR/elm-todomvc nix-build checkfile ./result/Main.html popd + +pushd test/source-directories/a-src + elm2nix init > default.nix + elm2nix convert > elm-srcs.nix + elm2nix snapshot > versions.dat + nix-build + checkfile ./result/Main.html +popd diff --git a/src/Elm2Nix.hs b/src/Elm2Nix.hs index 964400a..b8dd0de 100644 --- a/src/Elm2Nix.hs +++ b/src/Elm2Nix.hs @@ -17,13 +17,16 @@ import Data.List (intercalate) import Data.HashMap.Strict (HashMap) import Data.String.Here import Data.Text (Text) +import Data.Vector (Vector) import System.Exit (exitFailure) import System.IO (hPutStrLn, stderr) +import qualified System.Directory import qualified Data.HashMap.Strict as HM import qualified Data.ByteString.Lazy as LBS import qualified Data.Aeson as Json import qualified Data.Text as Text +import qualified Data.Vector as Vector import Elm2Nix.FixedOutput (FixedDerivation(..), prefetch) import Elm2Nix.PackagesSnapshot (snapshot) @@ -67,13 +70,35 @@ parseElmJsonDeps depsKey obj = parseDeps (Object hm) = mapM (uncurry parseDep) (HM.toList hm) parseDeps v = Left (UnexpectedValue v) +tryLookup :: HashMap Text Value -> Text -> Either Elm2NixError Value +tryLookup hm key = maybeToRight (KeyNotFound key) (HM.lookup key hm) + where maybeToRight :: b -> Maybe a -> Either b a maybeToRight _ (Just x) = Right x maybeToRight y Nothing = Left y - tryLookup :: HashMap Text Value -> Text -> Either Elm2NixError Value - tryLookup hm key = - maybeToRight (KeyNotFound key) (HM.lookup key hm) +parseElmJsonSrcs :: Value -> Either Elm2NixError (Vector FilePath) +parseElmJsonSrcs obj = + case obj of + Object hm -> do + case tryLookup hm "source-directories" of + Left _ -> Right Vector.empty + Right (Array vec) -> mapM extractSrcPath vec + Right v -> Left (UnexpectedValue v) + v -> Left $ UnexpectedValue v + where + extractSrcPath :: Value -> Either Elm2NixError String + extractSrcPath val = + case val of + String text -> Right (toNixPath (Text.unpack text)) + v -> Left (UnexpectedValue v) + + toNixPath :: FilePath -> FilePath + toNixPath path = + case path of + "." -> "./." + p@('.':_) -> p + p -> "./" <> p -- CMDs @@ -91,20 +116,50 @@ convert = runCLI $ do liftIO (putStrLn (generateNixSources sources)) initialize :: IO () -initialize = runCLI $ +initialize = runCLI $ do + liftIO (hPutStrLn stderr "Resolving elm.json source directories into Nix paths...") + res <- liftIO (fmap Json.eitherDecode (LBS.readFile "elm.json")) + elmJson <- either (throwErr . ElmJsonReadError) return res + + srcs' <- either throwErr return (parseElmJsonSrcs elmJson) + liftIO (hPutStrLn stderr $ "Using source directories:") + liftIO (mapM (hPutStrLn stderr) srcs') + let srcs = stringifySrcs srcs' + srcdir <- liftIO (getSrcDir srcs') + liftIO (putStrLn [template|data/default.nix|]) where -- | Converts Package.Name to Nix friendly name baseName :: Text baseName = "elm-app" + version :: Text version = "0.1.0" + toNixName :: Text -> Text toNixName = Text.replace "/" "-" + name :: String name = Text.unpack (toNixName baseName <> "-" <> version) - srcdir :: String - srcdir = "./src" -- TODO: get from elm.json + + getSrcDir :: Vector FilePath -> IO FilePath + getSrcDir dirs + | Vector.null dirs = pure "./src" + | "./." `Vector.elem` dirs || "." `Vector.elem` dirs = + -- Nix creates dir named after current directory if `srcs` contains `./.` + lastDir <$> liftIO System.Directory.getCurrentDirectory + | otherwise = + -- Can't fail as there is case for Vec.null! + pure (Vector.head dirs) + + lastDir :: FilePath -> FilePath + lastDir = foldl (\path c -> if c == '/' then "" else path <> [c]) "" + + stringifySrcs :: Vector FilePath -> String + stringifySrcs xs = + "[\n" + <> foldr (\i acc -> " " <> i <> "\n" <> acc) "" xs + <> " ]" -- Utils diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 0000000..da7980e --- /dev/null +++ b/test/.gitignore @@ -0,0 +1,2 @@ +*.nix +*.dat diff --git a/test/Spec.hs b/test/Spec.hs deleted file mode 100644 index cd4753f..0000000 --- a/test/Spec.hs +++ /dev/null @@ -1,2 +0,0 @@ -main :: IO () -main = putStrLn "Test suite not yet implemented" diff --git a/test/source-directories/a-src/Main.elm b/test/source-directories/a-src/Main.elm new file mode 100644 index 0000000..c0580c6 --- /dev/null +++ b/test/source-directories/a-src/Main.elm @@ -0,0 +1,6 @@ +module Main exposing (main) + +import Html exposing (..) +import Text + +main = text Text.hello diff --git a/test/source-directories/a-src/elm.json b/test/source-directories/a-src/elm.json new file mode 100644 index 0000000..3adbb7a --- /dev/null +++ b/test/source-directories/a-src/elm.json @@ -0,0 +1,25 @@ +{ + "type": "application", + "source-directories": [ + ".", + "../b-src/src" + ], + "elm-version": "0.19.0", + "dependencies": { + "direct": { + "elm/browser": "1.0.1", + "elm/core": "1.0.2", + "elm/html": "1.0.0" + }, + "indirect": { + "elm/json": "1.1.3", + "elm/time": "1.0.0", + "elm/url": "1.0.0", + "elm/virtual-dom": "1.0.2" + } + }, + "test-dependencies": { + "direct": {}, + "indirect": {} + } +} diff --git a/test/source-directories/b-src/src/Text.elm b/test/source-directories/b-src/src/Text.elm new file mode 100644 index 0000000..1a39c3f --- /dev/null +++ b/test/source-directories/b-src/src/Text.elm @@ -0,0 +1,3 @@ +module Text exposing (hello) + +hello = "Hello world!"