Если вы видите что-то необычное, просто сообщите мне. Skip to main content

Отчет по stack скриптованию: как и почему...

Введение

Почему stack скрипт?

Если вы делитесь маленьким, одиночным модулчем, самостоятельным примером haskell, то stack script дает нам простой способ получить воспроизводимую сбое построениеку, просто зафиксировав зависимости с помощью Stackage внутри комментариев в начале кода на Haskell.

Есть как минимум две дополнительные причины, кроме воспроизведения псбостроенкия приложений, возможно вам захочится использовать Stack скриптовый функционал:

  • LowerНизкий theконфигурационный configurationуровень: barrier:написание writeнезависимого anкомплириуемого independentlyфайла compilingс Haskell sourceкодом, codeс fileзависимостями withбез packageнеобходимости dependenciesнастроек without having to configure a newнового stack orили cabal project. Personally, I find this helpful when exploring new libraries or writing small programs.проекта.
  • UsingИспользование HaskellHashell asкак aя scriptingзыка language,скриптования, orизи replacementзамена forдля Shell/Bash/Zsh. ThisЭтот useспособ caseиспользования pairsобъединяется wellс with theиспользованием Turtle library,библиотеки, 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Просто Servant serverсервер thatкоторый staticallyраздает servesстатические yourданные currentвашей workingтекущей directoryпапки.
  • An example ofПример stack asкак aзамена bash replacement
  • UsingИспользование 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снимок.

snapshot.

Как 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-bins createсоздает aбинарник binaryосновываясь executableна basedимени on the file name.файла
  • installsустанавливает ghc, ifесли neededнеобходимо, withс помощью install-ghcgch
  • buildsсобирает 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!будущем.