Latest news about Bitcoin and all cryptocurrencies. Your daily crypto news habit.
In last weekâs article, we deployed our Haskell code to the cloud using Heroku. Our solution worked, but the process was also very basic and very manual. Letâs review the steps we would take to deploy code on a real project with this approach.
- Make a pull request against master branch
- Merge code into master
- Pull master locally, run tests
- Manually run git push heroku master
- Hope everything works fine on Heroku
This isnât a great approach. Wherever there are manual steps in our development process, weâre likely to forget something. This will almost always come around to bite us at some point. In this article, weâll see how we can automate our development workflow using Circle CI.
Getting Started with Circle
To follow along with this article, you should already have your project stored on Github. As soon as you have this, you can integrate with Circle easily. Go to the Circle Website and login with Github. Then go to âAdd Projectâ. You should see all your personal repositories. Clicking your Haskell project should allow you to integrate the two services.
Now that Circle knows about our repository, it will try to build whenever we push code up to Github. But we have to tell Circle CI what to do once weâve pushed our code! For this step, weâll need to create a config file and store it as part of our repository. Note weâll be using Version 2 of the Circle CI configuration. To define this configuration we first create a folder called .circleci at the root of our repository. Then we make a YAML file called config.yaml.
In Circle V2, we specify âworkflowsâ for the Circle container to run through. To keep things simple, weâll limit our actions to take place within a single workflow. We specify the workflows section at the bottom of our config:
workflows: version: 2 build_and_test: jobs: - build_project
Now at the top, weâll again specify version 2, and then lay out a bare-bones definition of our build_project job.
version: 2jobs: build_project: machine: true steps: - checkout - run: echo âHelloâ
The machine section indicates a default Circle machine image weâre using for our project. Thereâs no built-in Haskell machine configuration we can use, so weâre using a basic image. Then for our steps, weâll first checkout our code, and then run a simple âechoâ command. Letâs now consider how we can get this machine to get the Stack utility so we can actually go and build our code.
Installing Stack
So right now our Circle container has no Haskell tools. This means weâll need to do everything from scratch. This is a useful learning exercise. Weâll learn the minimal steps we need to take to build a Haskell project on a Linux box. Next week, weâll see a shortcut we can use.
Luckily, the Stack tool handles most of our problems for us, but we first have to download it. So after checking our our code, weâll run several different commands to install Stack. Hereâs what they look like:
steps: - checkout - run: wget https://github.com/commercialhaskell/stack/releases/download/v1.6.1/stack-1.6.1-linux-x86_64.tar.gz -O /tmp/stack.tar.gz - run: sudo mkdir /tmp/stack-download - run: sudo tar -xzf /tmp/stack.tar.gz -C /tmp/stack-download - run: sudo chmod +x /tmp/stack-download/stack-1.6.1-linux-x86_64/stack - run: sudo mv /tmp/stack-download/stack-1.6.1-linux-x86_64/stack /usr/bin/stack
The wget command downloads Stack off Github. If youâre using a different version of Stack than we are (1.6.1), youâll need to change the version numbers of course. Weâll then create a temporary directory to unzip the actual executable to. Then we use tar to perform the unzip step. This leaves us with the stack executable in the appropriate folder. Weâll give this executable x permissions, and then move it onto the machineâs path. Then we can use stack!
Building Our Project
Now weâve done most of the hard work! From here, weâll just use the Stack commands to make sure our code works. Weâll start by running stack setup. This will download whatever version of GHC our project needs. Then weâll run the stack test command to make sure our code compiles and passes all our test suites.
steps: - checkout - run: wget ⊠... - run: stack setup - run: stack test
Note that Circle expects our commands to finish with exit code 0. This means if any of them has a non-zero exit code, the build will be a âfailureâ. This includes our stack test step. Thus, if we push code that fails any of our tests, weâll see it as a build failure! This spares us the extra steps of running our tests manually and âhopingâ theyâll work on the environment we deploy to.
Caching
There is a pretty big weakness in this process right now. Every Circle container we make starts from scratch. Thus weâll have to download GHC and all the different libraries our code depends on for every build. This means you might need to wait 30â60 minutes to see if your code passes depending on the size of your project! We donât want this. So to make things faster, weâll tell Circle to cache this information, since it wonât change on most builds. Weâll take the following two steps:
- Only download GHC when stack.yaml changes (since the LTS might have changed). This involves caching the ~/.stack directory
- Only re-download libraries when either stack.yaml or our .cabal file changes. For this, weâll cache the .stack-work library.
For each of these, weâll make an appropriate cache key. At the start of our build process, weâll attempt to restore these directories from the cache based on particular keys. As part of each key, weâll use a checksum of the relevant file.
steps: - checkout - restore-cache: keys: - stack-{{ checksum âstack.yamlâ }} - restore-cache: keys: - stack-{{checksum âstack.yamlâ}}-{{checksum âproject.cabalâ}}
If these files change, the checksum will be different, so Circle wonât be able to restore the directories. Then our other steps will run in full, downloading all the relevant information. At the end of the process, we want to then make sure weâve saved these directories under the same key. We do this with the save_cache command:
steps: ⊠- stack test - save-cache: key: stack-{{ checksum âstack.yamlâ }} paths: - â~/.stackâ - restore-cache: keys: stack-{{checksum âstack.yamlâ}}-{{checksum âproject.cabalâ}} paths: - â.stack-workâ
Now the next builds wonât take as long! There are other ways we can make our cache keys. For instance, we could use the Stack LTS as part of the key, and bump this every time we change which LTS weâre using. The downside is that thereâs a little more manual work required. But this work wonât happen too often. The positive side is that we wonât need to re-download GHC when we add extra dependencies to stack.yaml.
Deploying to Heroku
Last but not least, weâll want to actually deploy our code to heroku every time we push to the master branch. Heroku makes it very easy for us to do this! First, go to the app dashboard for Heroku. Then find the Deploy tab. You should see an option to connect with Github. Use it to connect your repository. Then make sure you check the box that indicates Heroku should wait for CI. Now, whenever your build successfully completes, your code will get pushed to Heroku!
Conclusion
You might have noticed that thereâs some redundancy with our approaches now! Our Circle CI container will build the code. Then our Heroku container will also build the code! This is very inefficient, and it can lead to deployment problems down the line. Next week, weâll see how we can use Docker in this process. Docker fully integrates with Circle V2. It will simplify our Circle config definition. It will also spare us from needing to rebuild all our code on Heroku again!
With all these tools at your disposal, itâs time to finally build that Haskell app you always wanted to! Download our Production Checklist to learn some cool libraries you can use!
If youâve never programmed in Haskell before, hopefully you can see that itâs not too difficult to use! Download our Haskell Beginnerâs Checklist and get started!
Deploying Confidently: Haskell and Circle CI was originally published in Hacker Noon on Medium, where people are continuing the conversation by highlighting and responding to this story.
Disclaimer
The views and opinions expressed in this article are solely those of the authors and do not reflect the views of Bitcoin Insider. Every investment and trading move involves risk - this is especially true for cryptocurrencies given their volatility. We strongly advise our readers to conduct their own research when making a decision.