diff options
author | Ben Sima <ben@bsima.me> | 2020-11-17 16:24:29 -0500 |
---|---|---|
committer | Ben Sima <ben@bsima.me> | 2020-11-17 16:24:29 -0500 |
commit | ac3d455a9c0dc0b2f4afb88b56db3d16c0508428 (patch) | |
tree | 0c2c558fb9c281bc79eb5ea560ead68f6b0f6918 /Biz | |
parent | 3887308a3ad006b487f180d29b495c65294a7d26 (diff) |
Refactor bild logic a bit
This should make it easier to add nix builds, which is the next task. I
need to move some files and nix code around so that I have e.g.
Que/Prod.nix as the actual full 'build.os' expression.
Diffstat (limited to 'Biz')
-rw-r--r-- | Biz/Bild.hs | 311 |
1 files changed, 155 insertions, 156 deletions
diff --git a/Biz/Bild.hs b/Biz/Bild.hs index ee0d78a..92054f9 100644 --- a/Biz/Bild.hs +++ b/Biz/Bild.hs @@ -3,140 +3,141 @@ {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE NoImplicitPrelude #-} -{- | A general purpose build tool. - -Not all of the below design is implemented. Currently: - -- with a nix build, results are linked in _/bild/nix/<target> -- for a dev build, results are stored in _/bild/dev/<target> - ------------------------------------------------------------------------------ - -== Design constraints - - * only input is a namespace, no subcommands, no packages - - * no need to write specific build rules - - * one rule for hs, one for rs, one for scm, and so on - - * no need to distinguish between exe and lib, just have a single output - - * never concerned with deployment/packaging - leave that to another tool - (scp? tar?) - -== Features - - * namespace maps to filesystem - - * no need for `bild -l` for listing available targets. Use `ls` or `tree` - - * you build namespaces, not files/modules/packages/etc - - * namespace maps to language modules - - * build settings can be set in the file comments - - * pwd is always considered the the source directory, no `src` vs `doc` etc. - - * build methods automaticatly detected with file extensions - - * flags modify the way to interact with the build, some ideas: - - * -s = jump into a shell and/or repl - - * -p = turn on profiling - - * -t = limit build by type (file extension) - - * -e = exclude some regex in the ns tree - - * -o = optimize level - -== Example Commands - -> bild [-spt] <target..> - -The general scheme is to build the things described by the targets. A target -is a namespace. You can list as many as you want, but you must list at least -one. It could just be `.` for the current directory. Build outputs will go -into the _/bild directory in the root of the project. - -> bild a.b - -Or `bild a/b`. This shows building a file at ./a/b.hs, this will translate to -something like `ghc --make A.B`. - -> bild -s <target> - -Starts a repl/shell for target. - - if target.hs, load ghci - - if target.scm, load scheme repl - - if target.clj, load a clojure repl - - if target.nix, load nix-shell - - and so on. - -> bild -p <target> - -build target with profiling (if available) - -> bild -t nix target - -only build target.nix, not target.hs and so on (in the case of multiple -targets with the same name but different extension). - -== Build Metadata - -Metadata is set in the comments with a special syntax. For third-party deps, -we list the deps in comments in the target file, like: - -> -- : dep aeson - -The output executable is named with: - -> -- : exe my-program - -or - -> -- : exe my-ap.js - -When multiple compilers are possible (e.g. ghc vs ghcjs) we chose ghcjs when -the target exe ends in .js. - -This method of setting metadata in the module comments works pretty well, -and really only needs to be done in the entrypoint module anyway. - -Local module deps are included by just giving the repo root to the compiler -that bild calls out to. - -== Questions - - * how to handle multiple output formats? - - * e.g. that ghcjs and ghc take the same input files... - - * say you have a .md file, you want to bild it to pdf, html, and more. What - do? - --} +-- | A general purpose build tool. +-- +-- Not all of the below design is implemented. Currently: +-- +-- - with a nix build, results are linked in _/bild/nix/<target> +-- - for a dev build, results are stored in _/bild/dev/<target> +-- +-- ----------------------------------------------------------------------------- +-- +-- == Design constraints +-- +-- * only input is a namespace, no subcommands, no packages +-- +-- * no need to write specific build rules +-- +-- * one rule for hs, one for rs, one for scm, and so on +-- +-- * no need to distinguish between exe and lib, just have a single output +-- +-- * never concerned with deployment/packaging - leave that to another tool +-- (scp? tar?) +-- +-- == Features +-- +-- * namespace maps to filesystem +-- +-- * no need for `bild -l` for listing available targets. Use `ls` or `tree` +-- +-- * you build namespaces, not files/modules/packages/etc +-- +-- * namespace maps to language modules +-- +-- * build settings can be set in the file comments +-- +-- * pwd is always considered the the source directory, no `src` vs `doc` etc. +-- +-- * build methods automaticatly detected with file extensions +-- +-- * flags modify the way to interact with the build, some ideas: +-- +-- * -s = jump into a shell and/or repl +-- +-- * -p = turn on profiling +-- +-- * -t = limit build by type (file extension) +-- +-- * -e = exclude some regex in the ns tree +-- +-- * -o = optimize level +-- +-- == Example Commands +-- +-- > bild [-spt] <target..> +-- +-- The general scheme is to build the things described by the targets. A target +-- is a namespace. You can list as many as you want, but you must list at least +-- one. It could just be `.` for the current directory. Build outputs will go +-- into the _/bild directory in the root of the project. +-- +-- > bild A/B.hs +-- +-- This will build the file at ./A/B.hs, this will translate to something like +-- `ghc --make A.B`. +-- +-- > bild -s <target> +-- +-- Starts a repl/shell for target. +-- - if target.hs, load ghci +-- - if target.scm, load scheme repl +-- - if target.clj, load a clojure repl +-- - if target.nix, load nix-shell +-- - and so on. +-- +-- > bild -p <target> +-- +-- build target with profiling (if available) +-- +-- > bild -t nix target +-- +-- only build target.nix, not target.hs and so on (in the case of multiple +-- targets with the same name but different extension). +-- +-- == Build Metadata +-- +-- Metadata is set in the comments with a special syntax. For third-party deps, +-- we list the deps in comments in the target file, like: +-- +-- > -- : dep aeson +-- +-- The output executable is named with: +-- +-- > -- : exe my-program +-- +-- or +-- +-- > -- : exe my-ap.js +-- +-- When multiple compilers are possible (e.g. ghc vs ghcjs) we chose ghcjs when +-- the target exe ends in .js. +-- +-- This method of setting metadata in the module comments works pretty well, +-- and really only needs to be done in the entrypoint module anyway. +-- +-- Local module deps are included by just giving the repo root to the compiler +-- that bild calls out to. +-- +-- == Questions +-- +-- * how to handle multiple output formats? +-- +-- * e.g. that ghcjs and ghc take the same input files... +-- +-- * say you have a .md file, you want to bild it to pdf, html, and more. What +-- do? module Biz.Bild where -import Alpha hiding ((<.>), sym) +import Alpha hiding (sym, (<.>)) import qualified Data.Char as Char import qualified Data.List as List +import qualified Data.String as String import qualified Data.Text as Text import qualified System.Directory as Dir import qualified System.Environment as Env import qualified System.Exit as Exit import System.FilePath ((</>)) +import qualified System.FilePath as File import qualified System.Process as Process import qualified Text.Regex.Applicative as Regex import qualified Prelude main :: IO () -main = Env.getArgs /> head >>= \case - Nothing -> Exit.die "usage: bild <target>" - Just target -> analyze target >>= build +main = + Env.getArgs /> head >>= \case + Nothing -> Exit.die "usage: bild <target>" + Just target -> analyze target >>= build type Namespace = String @@ -147,50 +148,48 @@ type Exe = String data Compiler = Ghc | Ghcjs | Nix deriving (Show) -data Target - = Target - { -- | Output executable name - exe :: Exe, - -- | Fully qualified namespace partitioned by '.' - namespace :: Namespace, - -- | Absolute path to file - path :: FilePath, - -- | Parsed/detected dependencies - deps :: [Dep], - -- | Which compiler should we use? - compiler :: Compiler - } +data Target = Target + { -- | Output executable name + exe :: Exe, + -- | Fully qualified namespace partitioned by '.' + namespace :: Namespace, + -- | Absolute path to file + path :: FilePath, + -- | Parsed/detected dependencies + deps :: [Dep], + -- | Which compiler should we use? + compiler :: Compiler + } deriving (Show) analyze :: String -> IO Target analyze s = do root <- Env.getEnv "BIZ_ROOT" cwd <- Dir.getCurrentDirectory - -- this is a hack to support multiple file types. Ideally we would just detect - -- which file extensions exist, then return [Target], which can then be built - -- in parallel - let path = cwd </> reps "." "/" s |> reps "/hs" ".hs" |> reps "/nix" ".nix" - content <- lines </ Prelude.readFile path - let exe = content /> Regex.match metaExe |> catMaybes |> head |> require "exe" - return - Target - { namespace = - require "namespace" - <| path - |> reps root "" - |> reps ".hs" "" - |> reps ".nix" "" - |> reps "/" "." - |> List.stripPrefix "." - >>= Regex.match metaNamespace, + let path = cwd </> s + case File.takeExtension path of + ".hs" -> do + content <- String.lines </ Prelude.readFile path + let exe = content /> Regex.match metaExe |> catMaybes |> head |> require "exe" + let compiler = if ".js" `List.isSuffixOf` exe then Ghcjs else Ghc + return Target { + namespace = require "namespace" + <| path + |> reps root "" + |> File.dropExtension + |> reps "/" "." + |> List.stripPrefix "." + >>= Regex.match metaNamespace, deps = content /> Regex.match metaDep |> catMaybes, - compiler = - if ".hs" `List.isSuffixOf` path - then if ".js" `List.isSuffixOf` exe then Ghcjs else Ghc - else Nix, .. } + ".nix" -> return Target { + namespace = s, path = path, deps = [], compiler = Nix, exe = "" + } + + e -> panic <| "bild does not know this extension: " <> Text.pack e + build :: Target -> IO () build Target {..} = do root <- Env.getEnv "BIZ_ROOT" @@ -265,8 +264,8 @@ require s Nothing = panic <| s <> " not found" reps :: String -> String -> String -> String reps a b s@(x : xs) = if isPrefixOf a s - then-- then, write 'b' and replace jumping 'a' substring + then -- then, write 'b' and replace jumping 'a' substring b ++ reps a b (drop (length a) s) - else-- then, write 'x' char and try to replace tail string + else -- then, write 'x' char and try to replace tail string x : reps a b xs reps _ _ [] = [] |