Отчет по stack скриптованию: как и почему...
Введение
Почему stack скрипт?
Если вы делитесь маленьким, одиночным модулчем, самостоятельным примером haskell, то stack script дает нам простой способ получить воспроизводимую сбое построениеку, просто зафиксировав зависимости с помощью Stackage внутри комментариев в начале кода на Haskell.
Есть как минимум две дополнительные причины, кроме воспроизведения псбостроенкия приложений, возможно вам захочится использовать Stack скриптовый функционал:
LowerНизкийtheконфигурационныйconfigurationуровень:barrier:написаниеwriteнезависимогоanкомплириуемогоindependentlyфайлаcompilingс Haskellsourceкодом,codeсfileзависимостямиwithбезpackageнеобходимостиdependenciesнастроекwithout having to configure a newнового stackorили cabalproject. Personally, I find this helpful when exploring new libraries or writing small programs.проекта.UsingИспользованиеHaskellHashellasкакaяscriptingзыкаlanguage,скриптования,orизиreplacementзаменаforдля Shell/Bash/Zsh.ThisЭтотuseспособcaseиспользованияpairsобъединяетсяwellсwith theиспользованием Turtlelibrary,библиотеки,althoughтакthisжеapproachуdoesэтогоhaveподходаdownsides.есть недостатки.
AboutО статье
Stack isэто aинструмент buildпостроения, toolглавным primarilyобразом, designedразработанный forдля reproducibleвоспроизвдения builds,сборки doneприложений, byвыполняемое specifyingс aпомощью специального resolver
(разрешателя inзависимостей) aв configurationконфигурацийонном file,файле, usuallyобычно yourваш projectsпроект stack.yaml
andи package.yaml
. WithС Stack’sпомощью scriptingвозможностей feature,скриптования weStack, stillмы getможем reproducibleвоспроизвести buildsсборку byприложения specifyingуказывая aresolver
, resolver,но butперенося moveэту thisспецификацию specificationв toфайле theкоторый fileмы weсобираем areили compiling,как orаргумент asкомандной aстроки.
commandОтсюда, lineс argument.целью Therefore,упростить, forмы theпредположим sakeчто ofэти simplicity,скрипты we’llзапускаются assume that these scripts are run outside of aвне stack project,проекта, andи stack isвызывается invokedв inтой theже sameдиректории directoryчто asи the script file.скрипт.
Note:Заметка: WhenКогда runningмы aзапускаем stack scriptскрипт insideвнутри ofпроекта astack, важно принять во внимание, что stack project,прочитает it’sнастройки important to consider that stack will read settings from yourиз project.yaml andи stack.yaml, whichчто mayможно causeпривести issues.к проблемам.
CodeПримеры Examplesкода
OutlineСодеражние
ThisЭта articleстатья containsсодержить theследующие followingпримеры examplesиспользования ofскриптов using scripting withи stack:
AБазовыйbasicпримерexampleинтепретатораof the Scripting InterpreterскриптовA simpleПросто Servantserverсерверthatкоторыйstaticallyраздаетservesстатическиеyourданныеcurrentвашейworkingтекущейdirectoryпапки.An example ofПример stackasкакaзамена bashreplacementUsingИспользованиеstackскриптаscriptзаtoдляlaunchзапуска ghci
BasicБазовый example ofпример stack scriptскрипта
ForДля ourнашего firstпервого example,примера, we’llмы useбудем использовать stack toдля runзапуска aодного singleфайла fileнаписаного ofна Haskell sourceв codeвиде as a script.скрипта.
Here’sВот theисходный sourceкод, codeкоторый weмы wantхотим toзапустить, run,в inфайле aпод filedназванием called simple.
:hs:hs
main :: IO ()
main = putStrLn "compiled & run"
ToДля runего thisзапуска with theс stack scriptинтерпретатора, interpreter,мы weможем canвыполнить do the following:следующее:
$ stack script simple.hs --resolver lts-14.18
TheАргуменыт для resolver
argumentобязательны, isи mandatory,stack andскомпилирует Stackи willзапустить compileпростой and run the simple.hs
fileфайл immediatelyсразу afterпосле invocationтого usingкак theбудет вызван lts-14.18
Stackageснимок.
Как Alternatively,альтернатива, weмы canможем putсложить allвсю ofконфигурационную theинформацию configurationв informationсам intoscript, theкак scriptпоказано itself, like this:ниже:
{- stack script
--resolver lts-14.18
-}
main :: IO ()
main = putStrLn "compiled & run"
whichчто canможет beбыть compiledскомпилено andи runзапущенно withс $помощью:
stack simple.hs.hs
A simple Servant server
The “killer feature” for scripting with stack is probably the ability to pull in packages without having to a stack.yaml or This can probably be best seen with stack ghci, where the following command will drop you into a ghci repl where you have lens and text packages available from the specificied resolver.
stack ghci --package text --package lens --resolver lts-14.18 An example of this concept with the stack scripting engine, is a quick and dirty file server, explore.hs would be as follows:
~/projects/stack-script$ cat explore.hs
#!/usr/bin/env stack
{- stack script
--resolver nightly-2019-12-22
--install-ghc
--package "servant-server warp"
--ghc-options -Wall
-}
{-# LANGUAGE DataKinds, TypeOperators, TypeApplications #-}
module FileServer where
import Network.Wai.Handler.Warp( defaultSettings, runSettings, setBeforeMainLoop, setPort)
import Servant (Proxy(Proxy), Raw, serve, serveDirectoryWebApp)
main :: IO ()
main = runSettings settings . serve (Proxy @Raw) $ serveDirectoryWebApp "."
where port = 8080
msg = "serving on http://localhost:" ++ show port ++ "/{pathToFile}"
settings = setPort port $ setBeforeMainLoop (putStrLn msg) defaultSettings
Noting a couple of features
- --install-ghc is the flag to install ghc, if it is not already available.
- The addition of the hash bang, (line 1), #!/usr/bin/env stack, let’s you run this as an executable, $ ./explore.hs
- If running, this script will let you see it’s source code at localhost:8080/static/explore.hs, along with any other files within the current working directory the script was run.
- The snapshot here is a nightly from the day the script was written, nightly-2019-12-22, which ensures the most up to date version of libraries are used when the script is written while still pinning us to a specific snapshot.
- We pass in -Wall to ghc-options, and can give additional ghc options here.
On a fresh compilation, this will take a few minutes to run, as Stack needs to go and grab about 255Mb worth of source code in ~86 dependent packages, compile and link it in order for the above code to run. However, on subsequent runs, Stack can use a local cache of of the packages, and we can reproduce our project build without downloading and building all the dependencies!
StackПростой ScriptServant as a Bash Replacementсервер.
It’sМожно possible to useиспользовать haskell, andи Stackскриптовые scriptingвозможности feature,Stack, alongвместе with theс Turtle libraryблиблотекой asкак a drop in replacement forзамену shell scripting!скриптов.
ToДля doэтого this,нам weнужно needдобавить theследующие followingстроки atв theначало tophaskell of our Haskell file:файла:
#!/usr/bin/env stack
{- stack script
--compile
--copy-bins
--resolver lts-14.17
--install-ghc
--package "turtle text foldl async"
--ghc-options=-Wall
-}
ThisЭто stack scriptскрипт doesделает aпару couple of things:вещуй:
- --compile
andи --copy-binscreateсоздаетaбинарникbinaryосновываясьexecutableнаbasedимениon the file name.файла installsустанавливает ghc,ifеслиneededнеобходимо,withс помощьюinstall-
ghcgchbuildsсобираетtheскриптscriptsсwithнаборомtheпакетовset of packages fromиз lts-14.17
WithС tutle,помощью weturle, getмы aполучаем portableпереносимый wayспособ toдля to run externalзапуска shell commands,команд, andмне Iудалсоь wasсоздать able to create a niceотличную haskell programпрограмму toдля replace theзамены shell scriptскрипта, Iкоторый usedя toиспользовал automateдля theавтоматизации serverзадачь tasksдля neededразвертывания toэтого deploy this blog!блога.
TheОснова basicsмоего myскрипта deployразвертывания - turtle
, scriptкак areвидно asдальше, follows,ниже andпредставлен youполный can see the full example on github hereпример:
import qualified Turtle as Tu
import qualified Control.Foldl as L
import qualified Data.Text as T
import Control.Concurrent.Async
import System.IO
argParser :: Tu.Parser Tu.FilePath
argParser = Tu.argPath "html" "html destination directory"
main :: IO ()
main = do
-- 53 files copied over into destinationDir
hSetBuffering stdout NoBuffering
destinationDir <- Tu.options "Build blog and copy to directory" argParser
Tu.with (Tu.mktempdir "/tmp" "deploy") (mainLoop destinationDir)
OneОдна niceотличная thingштука aboutпро turtle
isэто the Tu.with
function,функция, whichкоторая letsпозволяет useзапускать runнашу ourmain
theлогику mainво logicвременной ofдиректории, ourкоторая programв withдальнейшем aощичаещтся temporaryпосле directoryзаврешения which is subsequently cleaned up after the mainLoop
function returns.
Despite turtle being a handy library, I did find some downsides - Use of FilePath, which uses a pretty clunky, String based file representation - Often times clunkier semantics than just writing bash: for instance, cp -r SRC TRG is requires a fold over the result of ls SRC and construction of an explicit cp with each file, instead, you need to use cptree, which took me a while to figure out, so it would be nice if the semantics matched better! - Turtle is a monolithic framework for interacting with OS through a set of mirrored shell commands trying to match coreutiles, and it’s tightly couple parts makes it not very easy to pick the parts you like, and disregard the rest!.
UsingИспользование stack scriptскрипта toдля runзапуска ghcighci.
We’veМы alreadyуже seenвидели a few examples ofпримеры stack script,скриптов, butно thereесть isеще, oneчто moreдолжно thatбыть shouldв beнаборе inразработчика every Haskeller’s toolkit.Haskell. Stack scriptскрипты canможно beиспользовать usedдля to launch aзапуска ghci repl. Let’sПредставим sayмы weработаем areнад working with a newновой ADT, andи wantмы toхотим writeнаписать aновый newобъект QuickCheck
, instance,как howнам canможет stackпомочь script help us?script?
TheСледующий followingзаголовок headerзагрузит willсписок loadприведенный theниже listed packages into aв ghci repl:
{- stack
--resolver nightly
--install-ghc
exec ghci
--package "QuickCheck checkers"
-}
module XTest where
ThereОтметим isеще oneпару noteвещей toо makeпорядке here about the order of the arguments:аргументов:
TheФайлfileскомпилируется,willиcompile,затемthenоткроетdropконсольyouсintoзагруженнымwithмодулемmodule XTest is loadedXTest.IfЕслиexec ghci
doesсразуnotжеimmediatelyнеfollowстоитstack,заthenstack
,theтогда--packages
mustдолженbeбытьbeforeпередexec ghci
ghcid
YouТеперь canможно runзапустить theскрипт aboveвыше stackс scriptпомощью withghcid, ghcidдля toполучения getпрактически nearlyпостоянной instantобратной compilerсвязи feedbackкомпилятора usingиспользуя theследующую following:команду:
bash$ ghcid -c "stack XTest.hs"
ConclusionЗаключение
IЯ oftenчасто findнахожу myselfсебя codingза upнаписанием smallмаленьких Haskellhaskell snippets,обрывков, whetherоднако it’sно playingобычно aroundэто withсвязано aс newизучением dataновых type,типов tryingданных, outиспользованием aбиблиотке, library,или orвоспроизвдения reproducingпримеров anиз exampleстатей fromили aкниг. paperВ orэтом aслучае, book.Stack
Inскриптовая theseвозможность cases,позволяет Stack’мне scriptingуказать featureзависимости shinesс atпомощью givingснимка meв aзаголовке selfфала, containedи fileне whereбеспокоится Iо canломающих specifyизменениях, theили dependenciesнастройках viaпроекта aсо snapshotвсеми inверными theзависимостями.
fileЯ header,должен andобратиться notк haveвам toтоварищи worryхаскеллята, aboutиспользовать breakingвозможность changes,stack orскриптов settingкогда upкто-то aделится projectсвоим withкодом allв theсети, correctчтобы dependencies.помочь Thus,остальным Iзапустить wouldих urgeкод myсегодня, fellowи Haskellersв toлюбое considerдругое usingвремя stack’sв scripting feature when they share code online, to help others run their code today, and keep that way far into the future!будущем.