├── .github
├── CODEOWNERS
└── workflows
│ ├── build.yaml
│ └── build-prod.yaml
├── cabal.project
├── .last-exported-commit
├── branding
├── nix-bootstrap.png
├── nix-bootstrap-circle.png
└── nix-bootstrap-email-signature.png
├── test
├── Spec.hs
├── Bootstrap
│ ├── Nix
│ │ ├── Expr
│ │ │ ├── NixpkgsSpec.hs
│ │ │ └── MkShellSpec.hs
│ │ ├── CommandSpec.hs
│ │ └── FlakeSpec.hs
│ ├── Data
│ │ ├── DevContainerSpec.hs
│ │ ├── PreCommitHookSpec.hs
│ │ ├── VersionSpec.hs
│ │ ├── Bootstrappable
│ │ │ ├── Go
│ │ │ │ └── ModfileSpec.hs
│ │ │ ├── Rust
│ │ │ │ ├── MainRsSpec.hs
│ │ │ │ ├── CargoLockSpec.hs
│ │ │ │ └── CargoTomlSpec.hs
│ │ │ ├── GitPodYmlSpec.hs
│ │ │ ├── VSCodeSettingsSpec.hs
│ │ │ ├── Elm
│ │ │ │ ├── IndexJsSpec.hs
│ │ │ │ ├── ElmJsonSpec.hs
│ │ │ │ ├── IndexHtmlSpec.hs
│ │ │ │ ├── MainElmSpec.hs
│ │ │ │ ├── PackageJsonSpec.hs
│ │ │ │ └── Review
│ │ │ │ │ ├── ConfigSpec.hs
│ │ │ │ │ └── ElmJsonSpec.hs
│ │ │ ├── Haskell
│ │ │ │ ├── LibHsSpec.hs
│ │ │ │ ├── PreludeHsSpec.hs
│ │ │ │ ├── MainHsSpec.hs
│ │ │ │ └── ServerHsSpec.hs
│ │ │ ├── VSCodeExtensionsSpec.hs
│ │ │ ├── GitignoreSpec.hs
│ │ │ ├── HaskellPackagesNixSpec.hs
│ │ │ ├── EnvrcSpec.hs
│ │ │ ├── BootstrapStateSpec.hs
│ │ │ └── NixPreCommitHookConfigSpec.hs
│ │ ├── ProjectNameSpec.hs
│ │ ├── ProjectTypeSpec.hs
│ │ └── Config
│ │ │ └── InternalSpec.hs
│ └── StateSpec.hs
├── Test
│ ├── Util
│ │ ├── CanDieOnError.hs
│ │ └── RunConfig.hs
│ └── Util.hs
└── PreludeSpec.hs
├── app
└── Main.hs
├── .vscode
├── extensions.json
└── settings.json
├── src
├── Bootstrap
│ ├── Data
│ │ ├── Config
│ │ │ └── Internal
│ │ │ │ ├── CurrentVersion.hs
│ │ │ │ ├── THHelpers.hs
│ │ │ │ └── TH.hs
│ │ ├── Bootstrappable
│ │ │ ├── Python
│ │ │ │ └── Requirements.hs
│ │ │ ├── Rust
│ │ │ │ ├── MainRs.hs
│ │ │ │ ├── CargoLock.hs
│ │ │ │ └── CargoToml.hs
│ │ │ ├── Elm
│ │ │ │ ├── IndexJs.hs
│ │ │ │ ├── IndexHtml.hs
│ │ │ │ ├── MainElm.hs
│ │ │ │ ├── ElmJson.hs
│ │ │ │ ├── PackageJson.hs
│ │ │ │ └── Review
│ │ │ │ │ ├── Config.hs
│ │ │ │ │ └── ElmJson.hs
│ │ │ ├── GitPodYml.hs
│ │ │ ├── VSCodeSettings.hs
│ │ │ ├── VSCodeExtensions.hs
│ │ │ ├── Go
│ │ │ │ └── Modfile.hs
│ │ │ ├── HaskellPackagesNix.hs
│ │ │ ├── SystemsNix.hs
│ │ │ ├── Haskell
│ │ │ │ ├── PreludeHs.hs
│ │ │ │ ├── LibHs.hs
│ │ │ │ └── MainHs.hs
│ │ │ ├── Envrc.hs
│ │ │ ├── BuildNix.hs
│ │ │ ├── BootstrapState.hs
│ │ │ └── GitlabCIConfig.hs
│ │ ├── DevContainer.hs
│ │ ├── HList.hs
│ │ ├── PreCommitHook.hs
│ │ ├── ContinuousIntegration.hs
│ │ ├── Target.hs
│ │ ├── Version.hs
│ │ ├── VSCodeExtension.hs
│ │ ├── ProjectName.hs
│ │ ├── Config.hs
│ │ └── GHCVersion.hs
│ ├── Nix
│ │ ├── Expr
│ │ │ ├── FlakeInputs.hs
│ │ │ ├── Nixpkgs.hs
│ │ │ ├── ReproducibleBuild
│ │ │ │ ├── Rust.hs
│ │ │ │ ├── Go.hs
│ │ │ │ ├── Haskell.hs
│ │ │ │ └── Java.hs
│ │ │ ├── PreCommitHooks.hs
│ │ │ ├── Python.hs
│ │ │ ├── ReproducibleBuild.hs
│ │ │ ├── Haskell.hs
│ │ │ ├── MkShell.hs
│ │ │ └── BuildInputs.hs
│ │ ├── Command.hs
│ │ └── Flake.hs
│ ├── Terminal
│ │ └── Icon.hs
│ ├── Monad.hs
│ ├── GitPod.hs
│ ├── Error.hs
│ ├── State.hs
│ ├── Unix.hs
│ └── Cli.hs
└── Prelude.hs
├── LICENSE
├── .devcontainer
├── devcontainer.json
├── docker-compose.yaml
└── Dockerfile
├── nix
├── systems.nix
├── haskell-env.nix
├── release.nix
└── pre-commit-hooks.nix
├── hie.yaml
├── .gitignore
├── vulnerability-whitelist.toml
├── .nix-bootstrap.dhall
├── scripts
└── build-binary-cache.sh
├── .envrc
├── flake.lock
├── package.yaml
├── flake.nix
├── CODE_OF_CONDUCT.md
└── CONTRIBUTING.md
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @sd234678
2 |
--------------------------------------------------------------------------------
/cabal.project:
--------------------------------------------------------------------------------
1 | packages:
2 | .
3 |
--------------------------------------------------------------------------------
/.last-exported-commit:
--------------------------------------------------------------------------------
1 | Last exported commit from parent repo: 2565232df51867a537996c7597dba7e452a643c8
--------------------------------------------------------------------------------
/branding/nix-bootstrap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gchq/nix-bootstrap/HEAD/branding/nix-bootstrap.png
--------------------------------------------------------------------------------
/branding/nix-bootstrap-circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gchq/nix-bootstrap/HEAD/branding/nix-bootstrap-circle.png
--------------------------------------------------------------------------------
/branding/nix-bootstrap-email-signature.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gchq/nix-bootstrap/HEAD/branding/nix-bootstrap-email-signature.png
--------------------------------------------------------------------------------
/test/Spec.hs:
--------------------------------------------------------------------------------
1 | {-# OPTIONS_GHC -F -pgmF hspec-discover #-}
2 | {-# OPTIONS_GHC -Wno-missing-export-lists #-}
3 | {-# OPTIONS_GHC -Wno-missing-import-lists #-}
4 |
--------------------------------------------------------------------------------
/app/Main.hs:
--------------------------------------------------------------------------------
1 | -- | Copyright : (c) Crown Copyright GCHQ
2 | module Main (main) where
3 |
4 | import Bootstrap (nixBootstrap)
5 |
6 | main :: IO ()
7 | main = nixBootstrap
8 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "haskell.haskell",
4 | "arrterian.nix-env-selector",
5 | "jnoortheen.nix-ide",
6 | "arcanis.vscode-zipfs"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "nixEnvSelector.nixFile": "${workspaceRoot}/shell.nix",
3 | "nix.enableLanguageServer": true,
4 | "nix.formatterPath": "alejandra",
5 | "haskell.manageHLS": "PATH",
6 | "haskell.serverExecutablePath": "haskell-language-server-wrapper",
7 | "search.exclude": {
8 | "**/.yarn": true,
9 | "**/.pnp.*": true
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/Bootstrap/Data/Config/Internal/CurrentVersion.hs:
--------------------------------------------------------------------------------
1 | -- | Copyright : (c) Crown Copyright GCHQ
2 | -- Description : Defines the most recent config version, from which
3 | -- many of the config types are generated.
4 | module Bootstrap.Data.Config.Internal.CurrentVersion (currentVersionNumber, versionUniverse) where
5 |
6 | -- | The most recent version of the config
7 | currentVersionNumber :: Int
8 | currentVersionNumber = 11
9 |
10 | versionUniverse :: [Int]
11 | versionUniverse = [1 .. currentVersionNumber]
12 |
--------------------------------------------------------------------------------
/test/Bootstrap/Nix/Expr/NixpkgsSpec.hs:
--------------------------------------------------------------------------------
1 | -- | Copyright : (c) Crown Copyright GCHQ
2 | module Bootstrap.Nix.Expr.NixpkgsSpec (spec) where
3 |
4 | import Bootstrap.Nix.Expr (isMostlyCorrectlyScoped)
5 | import Bootstrap.Nix.Expr.Nixpkgs (nixpkgsExpr)
6 | import Test.Hspec (Spec, describe, it)
7 | import Test.Hspec.Expectations.Pretty (shouldBe)
8 |
9 | spec :: Spec
10 | spec = do
11 | describe "nixpkgsExpr" do
12 | it "has no external scope requirements" do
13 | isMostlyCorrectlyScoped nixpkgsExpr `shouldBe` Right ()
14 |
--------------------------------------------------------------------------------
/test/Test/Util/CanDieOnError.hs:
--------------------------------------------------------------------------------
1 | {-# OPTIONS_GHC -Wno-orphans #-}
2 |
3 | -- | Description : This module provides an alternate instance for
4 | -- CanDieOnError which doesn't require connection to a terminal.
5 | -- Copyright : (c) Crown Copyright GCHQ
6 | module Test.Util.CanDieOnError () where
7 |
8 | import Bootstrap.Error (CanDieOnError (dieOnError))
9 |
10 | instance {-# OVERLAPPING #-} CanDieOnError IO where
11 | dieOnError toErr a =
12 | runExceptT a >>= \case
13 | Right v -> pure v
14 | Left e -> error (toErr e)
15 |
--------------------------------------------------------------------------------
/test/Bootstrap/Nix/CommandSpec.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE TypeApplications #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | module Bootstrap.Nix.CommandSpec (spec) where
5 |
6 | import Bootstrap.Nix.Command
7 | ( NixCommand (NixCommand),
8 | NixCommandVariant (NCVBuild),
9 | writeNixCommand,
10 | )
11 | import Test.Hspec (Spec, describe, it, shouldBe)
12 |
13 | spec :: Spec
14 | spec = describe "writeNixCommand" do
15 | it "correctly writes build command" $
16 | writeNixCommand @Text (NixCommand NCVBuild) `shouldBe` "nix build"
17 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | © Crown Copyright GCHQ
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/src/Bootstrap/Nix/Expr/FlakeInputs.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | -- Description : Manages inputs for bootstrapped flakes
5 | module Bootstrap.Nix.Expr.FlakeInputs (nixpkgsSrcInputBinding) where
6 |
7 | import Bootstrap.Data.Target (Target (TargetDefault))
8 | import Bootstrap.Nix.Expr
9 | ( Binding,
10 | nix,
11 | nixproperty,
12 | (|=),
13 | )
14 |
15 | nixpkgsSrcInputBinding :: Target -> Binding
16 | nixpkgsSrcInputBinding target =
17 | [nixproperty|nixpkgs-src.url|] |= case target of
18 | TargetDefault -> [nix|"nixpkgs/25.11"|]
19 |
--------------------------------------------------------------------------------
/src/Bootstrap/Data/Bootstrappable/Python/Requirements.hs:
--------------------------------------------------------------------------------
1 | -- | Copyright : (c) Crown Copyright GCHQ
2 | module Bootstrap.Data.Bootstrappable.Python.Requirements (Requirements (Requirements)) where
3 |
4 | import Bootstrap.Data.Bootstrappable (Bootstrappable (bootstrapContent, bootstrapName, bootstrapReason))
5 |
6 | data Requirements = Requirements
7 |
8 | instance Bootstrappable Requirements where
9 | bootstrapName = const "requirements.txt"
10 | bootstrapReason = const "A file to contain python modules to be installed"
11 | bootstrapContent = const . pure $ Right "# Add your python dependencies to this file:"
12 |
--------------------------------------------------------------------------------
/test/Test/Util/RunConfig.hs:
--------------------------------------------------------------------------------
1 | -- | Copyright : (c) Crown Copyright GCHQ
2 | module Test.Util.RunConfig (rcDefault) where
3 |
4 | import Bootstrap.Cli
5 | ( RunConfig
6 | ( RunConfig,
7 | rcAllowDirty,
8 | rcFromScratch,
9 | rcNonInteractive,
10 | rcWithDevContainer
11 | ),
12 | )
13 |
14 | -- | A RunConfig with all its fields set to the simplest possible values
15 | rcDefault :: RunConfig
16 | rcDefault =
17 | RunConfig
18 | { rcAllowDirty = False,
19 | rcFromScratch = False,
20 | rcNonInteractive = False,
21 | rcWithDevContainer = Nothing
22 | }
23 |
--------------------------------------------------------------------------------
/src/Bootstrap/Data/DevContainer.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE GeneralisedNewtypeDeriving #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | module Bootstrap.Data.DevContainer (DevContainerConfig (..), devContainerConfigCodec) where
5 |
6 | import Dhall (FromDhall, ToDhall)
7 | import Toml (TomlCodec)
8 | import qualified Toml
9 |
10 | newtype DevContainerConfig = DevContainerConfig {unDevContainerConfig :: Bool}
11 | deriving newtype (FromDhall, ToDhall)
12 | deriving stock (Eq, Show)
13 |
14 | devContainerConfigCodec :: TomlCodec DevContainerConfig
15 | devContainerConfigCodec = Toml.diwrap (Toml.bool "setUpDevContainer")
16 |
--------------------------------------------------------------------------------
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nix-bootstrap DevContainer",
3 | "dockerComposeFile": "docker-compose.yaml",
4 | "extensions": [
5 | "arrterian.nix-env-selector",
6 | "haskell.haskell",
7 | "jnoortheen.nix-ide"
8 | ],
9 | "postCreateCommand": "git config --global core.editor 'code --wait' && git config --global --add safe.directory /workspaces/nix-bootstrap && direnv allow && nix-shell --run setUpHaskellLanguageServer",
10 | "postStartCommand": "nix-shell --run \"echo 'Setup complete!'\"",
11 | "service": "nix-bootstrap-dev-container",
12 | "workspaceFolder": "/workspaces/nix-bootstrap"
13 | }
14 |
--------------------------------------------------------------------------------
/src/Bootstrap/Data/HList.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE DataKinds #-}
2 | {-# LANGUAGE TypeFamilies #-}
3 | {-# LANGUAGE TypeOperators #-}
4 |
5 | -- | Description : Defines a type for heterogeneous lists.
6 | -- Copyright : (c) Crown Copyright GCHQ
7 | module Bootstrap.Data.HList (HList (..), (~:)) where
8 |
9 | -- | Represents a heterogeneous list (a list where the type of
10 | -- each element can be different).
11 | data HList :: [Type] -> Type where
12 | HNil :: HList '[]
13 | HCons :: x -> HList xs -> HList (x ': xs)
14 |
15 | infixr 5 ~:
16 |
17 | -- | Operator alias for `HCons`.
18 | (~:) :: x -> HList xs -> HList (x ': xs)
19 | (~:) = HCons
20 |
--------------------------------------------------------------------------------
/test/Bootstrap/Data/DevContainerSpec.hs:
--------------------------------------------------------------------------------
1 | -- | Copyright : (c) Crown Copyright GCHQ
2 | module Bootstrap.Data.DevContainerSpec (spec) where
3 |
4 | import Bootstrap.Data.DevContainer
5 | ( DevContainerConfig (DevContainerConfig),
6 | devContainerConfigCodec,
7 | )
8 | import Test.Hspec (Spec, describe, it)
9 | import Test.Util (tomlRoundtripTest)
10 |
11 | spec :: Spec
12 | spec = describe "DevContainerConfig" do
13 | it "roundtrips to TOML when True" $ tomlRoundtripTest devContainerConfigCodec (DevContainerConfig True)
14 | it "roundtrips to TOML when False" $ tomlRoundtripTest devContainerConfigCodec (DevContainerConfig False)
15 |
--------------------------------------------------------------------------------
/src/Bootstrap/Data/PreCommitHook.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE GeneralisedNewtypeDeriving #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | module Bootstrap.Data.PreCommitHook (PreCommitHooksConfig (..), preCommitHooksConfigCodec) where
5 |
6 | import Dhall (FromDhall, ToDhall)
7 | import Toml (TomlCodec)
8 | import qualified Toml
9 |
10 | newtype PreCommitHooksConfig = PreCommitHooksConfig {unPreCommitHooksConfig :: Bool}
11 | deriving newtype (FromDhall, ToDhall)
12 | deriving stock (Eq, Show)
13 |
14 | preCommitHooksConfigCodec :: TomlCodec PreCommitHooksConfig
15 | preCommitHooksConfigCodec = Toml.diwrap (Toml.bool "setUpPreCommitHooks")
16 |
--------------------------------------------------------------------------------
/test/Bootstrap/Data/PreCommitHookSpec.hs:
--------------------------------------------------------------------------------
1 | -- | Copyright : (c) Crown Copyright GCHQ
2 | module Bootstrap.Data.PreCommitHookSpec (spec) where
3 |
4 | import Bootstrap.Data.PreCommitHook
5 | ( PreCommitHooksConfig (PreCommitHooksConfig),
6 | preCommitHooksConfigCodec,
7 | )
8 | import Test.Hspec (Spec, describe, it)
9 | import Test.Util (tomlRoundtripTest)
10 |
11 | spec :: Spec
12 | spec = describe "PreCommitHooksConfig" do
13 | it "roundtrips to TOML when True" $ tomlRoundtripTest preCommitHooksConfigCodec (PreCommitHooksConfig True)
14 | it "roundtrips to TOML when False" $ tomlRoundtripTest preCommitHooksConfigCodec (PreCommitHooksConfig False)
15 |
--------------------------------------------------------------------------------
/src/Bootstrap/Terminal/Icon.hs:
--------------------------------------------------------------------------------
1 | -- | Copyright : (c) Crown Copyright GCHQ
2 | module Bootstrap.Terminal.Icon (icon) where
3 |
4 | icon :: [Text]
5 | icon =
6 | [ " #######",
7 | "##########",
8 | "[=] #######",
9 | " | #######",
10 | "[=] #######",
11 | " | #######",
12 | "[=] ########",
13 | " | #######",
14 | "[=] ########",
15 | " | ##########",
16 | "-+=+ #############",
17 | " '|| ####### ########",
18 | " `| ####### ,#######",
19 | "`==` ######## ########",
20 | " ######## #######",
21 | " Welcome to nix-bootstrap!"
22 | ]
23 |
--------------------------------------------------------------------------------
/test/PreludeSpec.hs:
--------------------------------------------------------------------------------
1 | -- | Copyright : (c) Crown Copyright GCHQ
2 | module PreludeSpec (spec) where
3 |
4 | import Test.Hspec (Spec, describe, it)
5 | import Test.Hspec.Expectations.Pretty (shouldBe)
6 |
7 | spec :: Spec
8 | spec = describe "clamp" do
9 | it "successfully clamps below the lower end" $ clamp 5 10 0 `shouldBe` (5 :: Int)
10 | it "successfully clamps above the upper end" $ clamp 5 10 100 `shouldBe` (10 :: Int)
11 | it "does not modify on the lower end" $ clamp 5 10 5 `shouldBe` (5 :: Int)
12 | it "does not modify on the upper end" $ clamp 5 10 10 `shouldBe` (10 :: Int)
13 | it "does not modify in the allowed range" $ clamp 5 10 7 `shouldBe` (7 :: Int)
14 |
--------------------------------------------------------------------------------
/src/Prelude.hs:
--------------------------------------------------------------------------------
1 | {-# OPTIONS_GHC -Wno-missing-import-lists #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | module Prelude (module Relude, clamp) where
5 |
6 | import Relude hiding (putText, putTextLn, state)
7 |
8 | -- | Given a minimum value and a maximum value, clamp a value to that range
9 | -- (values less than the minimum map to the minimum and values greater than
10 | -- the maximum map to the maximum).
11 | --
12 | -- >>> clamp 1 10 11
13 | -- 10
14 | -- >>> clamp 1 10 2
15 | -- 2
16 | -- >>> clamp 5 10 1
17 | -- 5
18 | clamp ::
19 | (Ord a) =>
20 | -- | The minimum value
21 | a ->
22 | -- | The maximum value
23 | a ->
24 | -- | The value to clamp
25 | a ->
26 | a
27 | clamp mn mx val = max mn (min val mx)
28 |
--------------------------------------------------------------------------------
/src/Bootstrap/Data/ContinuousIntegration.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE GeneralisedNewtypeDeriving #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | module Bootstrap.Data.ContinuousIntegration
5 | ( ContinuousIntegrationConfig (..),
6 | continuousIntegrationConfigCodec,
7 | )
8 | where
9 |
10 | import Dhall (FromDhall, ToDhall)
11 | import Toml (TomlCodec)
12 | import qualified Toml
13 |
14 | newtype ContinuousIntegrationConfig = ContinuousIntegrationConfig {unContinuousIntegrationConfig :: Bool}
15 | deriving newtype (FromDhall, ToDhall)
16 | deriving stock (Eq, Show)
17 |
18 | continuousIntegrationConfigCodec :: TomlCodec ContinuousIntegrationConfig
19 | continuousIntegrationConfigCodec = Toml.diwrap (Toml.bool "setUpContinuousIntegration")
20 |
--------------------------------------------------------------------------------
/test/Test/Util.hs:
--------------------------------------------------------------------------------
1 | -- | Copyright : (c) Crown Copyright GCHQ
2 | module Test.Util (dhallRoundtripTest, tomlRoundtripTest) where
3 |
4 | import Dhall
5 | ( Encoder (embed),
6 | FromDhall,
7 | ToDhall,
8 | auto,
9 | inject,
10 | input,
11 | )
12 | import Dhall.Core (pretty)
13 | import Test.Hspec.Expectations.Pretty (Expectation, shouldBe)
14 | import Toml (TomlCodec)
15 | import qualified Toml
16 |
17 | dhallRoundtripTest :: (Eq a, FromDhall a, Show a, ToDhall a) => a -> Expectation
18 | dhallRoundtripTest a = input auto (pretty $ embed inject a) >>= (`shouldBe` a)
19 |
20 | tomlRoundtripTest :: (Show a, Eq a) => TomlCodec a -> a -> Expectation
21 | tomlRoundtripTest codec a = Toml.decode codec (Toml.encode codec a) `shouldBe` Right a
22 |
--------------------------------------------------------------------------------
/nix/systems.nix:
--------------------------------------------------------------------------------
1 | # A minimal subset of numtide/flake-utils
2 | {
3 | forEachSystem = systems: f:
4 | builtins.foldl' (
5 | attrs: system: let
6 | ret = f system;
7 | in
8 | builtins.foldl' (
9 | attrs: key:
10 | attrs
11 | // {
12 | ${key} =
13 | (attrs.${key} or {})
14 | // {
15 | ${system} = ret.${key};
16 | };
17 | }
18 | )
19 | attrs (builtins.attrNames ret)
20 | ) {}
21 | systems;
22 |
23 | system = allSystems:
24 | builtins.listToAttrs
25 | (builtins.map (system: {
26 | name = system;
27 | value = system;
28 | })
29 | allSystems);
30 | }
31 |
--------------------------------------------------------------------------------
/src/Bootstrap/Nix/Command.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE ScopedTypeVariables #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | module Bootstrap.Nix.Command
5 | ( NixCommandVariant (..),
6 | NixCommand (NixCommand),
7 | writeNixCommand,
8 | )
9 | where
10 |
11 | data NixCommandVariant = NCVBuild
12 |
13 | newtype NixCommand = NixCommand
14 | { ncVariant :: NixCommandVariant
15 | }
16 |
17 | -- | Prints a nix command in the form it would be used in a shell.
18 | --
19 | -- >>> writeNixCommand (NixCommand NCVBuild)
20 | -- nix build
21 | writeNixCommand :: forall s. (IsString s, Semigroup s) => NixCommand -> s
22 | writeNixCommand NixCommand {..} = "nix " <> commandPart
23 | where
24 | commandPart :: s
25 | commandPart = case ncVariant of
26 | NCVBuild -> "build"
27 |
--------------------------------------------------------------------------------
/src/Bootstrap/Data/Config/Internal/THHelpers.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE TemplateHaskellQuotes #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | -- Description : Common TH functions for use in ...Config.Internal
5 | module Bootstrap.Data.Config.Internal.THHelpers (isoForName) where
6 |
7 | import Control.Lens (iso)
8 | import Language.Haskell.TH
9 | ( ExpQ,
10 | Name,
11 | Quote (newName),
12 | appE,
13 | conE,
14 | conP,
15 | lamE,
16 | varE,
17 | varP,
18 | )
19 |
20 | -- | Creates an iso like
21 | -- iso (\(VPT9 x) -> x) VPT9
22 | -- if VPT9 is the constructor name
23 | isoForName :: Name -> ExpQ
24 | isoForName constructorName = do
25 | x <- newName "x"
26 | [|iso|] `appE` lamE [conP constructorName [varP x]] (varE x) `appE` conE constructorName
27 |
--------------------------------------------------------------------------------
/test/Bootstrap/Data/VersionSpec.hs:
--------------------------------------------------------------------------------
1 | -- | Copyright : (c) Crown Copyright GCHQ
2 | module Bootstrap.Data.VersionSpec (spec) where
3 |
4 | import Bootstrap.Data.Version (toMajorVersion)
5 | import qualified Data.Version as V
6 | import qualified Relude.Unsafe as Unsafe
7 | import Test.Hspec (Spec, describe, it, shouldSatisfy)
8 |
9 | spec :: Spec
10 | spec = describe "MajorVersion's Ord instance" $
11 | it "orders versions correctly" do
12 | let mkVersion = Unsafe.fromJust . toMajorVersion . V.makeVersion
13 | a = mkVersion [0, 1, 0]
14 | b = mkVersion [1, 0, 0]
15 | c = mkVersion [1, 0, 1]
16 | d = mkVersion [2, 0, 1]
17 | a `shouldSatisfy` (<= b)
18 | b `shouldSatisfy` (<= c)
19 | c `shouldSatisfy` (<= d)
20 | a `shouldSatisfy` (<= d)
21 | a `shouldSatisfy` (<= a)
22 |
--------------------------------------------------------------------------------
/test/Bootstrap/Data/Bootstrappable/Go/ModfileSpec.hs:
--------------------------------------------------------------------------------
1 | -- | Copyright : (c) Crown Copyright GCHQ
2 | module Bootstrap.Data.Bootstrappable.Go.ModfileSpec (spec) where
3 |
4 | import Bootstrap.Data.Bootstrappable (Bootstrappable (bootstrapContent))
5 | import Bootstrap.Data.Bootstrappable.Go.Modfile (GoModfile (GoModfile))
6 | import Bootstrap.Data.ProjectName (mkProjectName)
7 | import qualified Relude.Unsafe as Unsafe
8 | import Test.Hspec (Spec, describe, it)
9 | import Test.Hspec.Expectations.Pretty (shouldBe)
10 |
11 | spec :: Spec
12 | spec = describe "go.mod rendering" do
13 | it "renders correctly given a dummy version" do
14 | let projectName = Unsafe.fromJust $ mkProjectName "test-project"
15 | bootstrapContent (GoModfile projectName "dummy-version")
16 | >>= (`shouldBe` Right "module test-project\n\ngo dummy-version\n")
17 |
--------------------------------------------------------------------------------
/test/Bootstrap/Data/Bootstrappable/Rust/MainRsSpec.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | module Bootstrap.Data.Bootstrappable.Rust.MainRsSpec (spec) where
5 |
6 | import Bootstrap.Data.Bootstrappable (Bootstrappable (bootstrapContent))
7 | import Bootstrap.Data.Bootstrappable.Rust.MainRs
8 | ( mainRsFor,
9 | )
10 | import Bootstrap.Data.ProjectType
11 | ( ProjectType (Rust),
12 | )
13 | import Test.Hspec (Spec, describe, it)
14 | import Test.Hspec.Expectations.Pretty (shouldBe)
15 | import Text.RawString.QQ (r)
16 |
17 | spec :: Spec
18 | spec = describe "main.rs rendering" do
19 | it "renders correctly" do
20 | bootstrapContent (mainRsFor Rust)
21 | >>= ( `shouldBe`
22 | Right
23 | [r|fn main() {
24 | println!("Hello, world!");
25 | }
26 | |]
27 | )
28 |
--------------------------------------------------------------------------------
/src/Bootstrap/Nix/Expr/Nixpkgs.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 | {-# LANGUAGE TemplateHaskellQuotes #-}
3 |
4 | -- | Copyright : (c) Crown Copyright GCHQ
5 | module Bootstrap.Nix.Expr.Nixpkgs (nixpkgsBinding, nixpkgsExpr) where
6 |
7 | import Bootstrap.Nix.Expr (Binding, Expr, nix, nixproperty, (|=))
8 |
9 | -- | nixpkgs = `nixpkgsExpr`;
10 | nixpkgsBinding :: Binding
11 | nixpkgsBinding = [nixproperty|nixpkgs|] |= nixpkgsExpr
12 |
13 | -- | An expression which imports nixpkgs from the flake.lock generated by the intermediate flake
14 | nixpkgsExpr :: Expr
15 | nixpkgsExpr =
16 | [nix|
17 | let lock = builtins.fromJSON (builtins.readFile ./flake.lock);
18 | in import (builtins.fetchTarball {
19 | url = "https://github.com/NixOS/nixpkgs/tarball/${lock.nodes.nixpkgs-src.locked.rev}";
20 | sha256 = lock.nodes.nixpkgs-src.locked.narHash;
21 | }) {}|]
22 |
--------------------------------------------------------------------------------
/src/Bootstrap/Data/Bootstrappable/Rust/MainRs.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | module Bootstrap.Data.Bootstrappable.Rust.MainRs
5 | ( MainRs,
6 | mainRsFor,
7 | )
8 | where
9 |
10 | import Bootstrap.Data.Bootstrappable
11 | ( Bootstrappable (bootstrapContent, bootstrapName, bootstrapReason),
12 | )
13 | import Bootstrap.Data.ProjectType
14 | ( ProjectType (Rust),
15 | )
16 | import Text.RawString.QQ (r)
17 |
18 | data MainRs = MainRs
19 |
20 | instance Bootstrappable MainRs where
21 | bootstrapName = const "src/main.rs"
22 | bootstrapReason = const "Your rust application's entrypoint"
23 | bootstrapContent MainRs =
24 | pure $
25 | Right
26 | [r|fn main() {
27 | println!("Hello, world!");
28 | }
29 | |]
30 |
31 | mainRsFor :: ProjectType -> Maybe MainRs
32 | mainRsFor = \case
33 | Rust -> Just MainRs
34 | _ -> Nothing
35 |
--------------------------------------------------------------------------------
/src/Bootstrap/Data/Target.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE DataKinds #-}
2 | {-# LANGUAGE DeriveGeneric #-}
3 | {-# LANGUAGE DerivingVia #-}
4 |
5 | -- | Copyright : (c) Crown Copyright GCHQ
6 | -- Description : A representation of the system to which we're bootstrapping
7 | module Bootstrap.Data.Target (Target (..), TargetV1 (..)) where
8 |
9 | import Dhall (FromDhall, ToDhall)
10 | import Dhall.Deriving (AsIs, Codec (Codec), Constructor, DropPrefix)
11 |
12 | -- | The system to which we're bootstrapping.
13 | --
14 | -- For public purposes this will always be `TargetDefault`.
15 | data Target
16 | = TargetDefault
17 | deriving stock (Eq, Generic, Show)
18 | deriving (FromDhall, ToDhall) via Codec (Constructor AsIs) Target
19 |
20 | -- | An older representation of Target
21 | data TargetV1
22 | = V1TargetDefault
23 | deriving stock (Eq, Generic, Show)
24 | deriving (FromDhall, ToDhall) via Codec (Constructor (DropPrefix "V1")) TargetV1
25 |
--------------------------------------------------------------------------------
/src/Bootstrap/Monad.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE ConstraintKinds #-}
2 | {-# LANGUAGE FlexibleContexts #-}
3 | {-# LANGUAGE UndecidableInstances #-}
4 | {-# OPTIONS_GHC -Wno-orphans #-}
5 |
6 | -- | Copyright : (c) Crown Copyright GCHQ
7 | module Bootstrap.Monad (MonadBootstrap) where
8 |
9 | import Control.Monad.Catch (MonadCatch, MonadMask)
10 | import Language.Haskell.TH (Quote (newName))
11 | import System.Terminal
12 | ( MonadColorPrinter,
13 | MonadFormattingPrinter,
14 | MonadInput,
15 | MonadMarkupPrinter,
16 | MonadScreen,
17 | TerminalT,
18 | )
19 |
20 | type MonadBootstrap m =
21 | ( MonadCatch m,
22 | MonadColorPrinter m,
23 | MonadFormattingPrinter m,
24 | MonadInput m,
25 | MonadIO m,
26 | MonadMarkupPrinter m,
27 | MonadMask m,
28 | MonadScreen m,
29 | Quote m
30 | )
31 |
32 | instance (Monad a, MonadIO (TerminalT m a)) => Quote (TerminalT m a) where
33 | newName = liftIO . newName
34 |
--------------------------------------------------------------------------------
/test/Bootstrap/Data/Bootstrappable/GitPodYmlSpec.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | module Bootstrap.Data.Bootstrappable.GitPodYmlSpec (spec) where
5 |
6 | import Bootstrap.Data.Bootstrappable (Bootstrappable (bootstrapContent))
7 | import Bootstrap.Data.Bootstrappable.GitPodYml (gitPodYmlFor)
8 | import Bootstrap.Data.ProjectType
9 | ( NodePackageManager (NPM),
10 | ProjectType (Node),
11 | )
12 | import Test.Hspec (Spec, describe, it)
13 | import Test.Hspec.Expectations.Pretty (shouldBe)
14 | import Text.RawString.QQ (r)
15 |
16 | spec :: Spec
17 | spec = describe ".gitpod.yml rendering" do
18 | it "renders the yml correctly" do
19 | bootstrapContent (gitPodYmlFor (Node NPM))
20 | >>= ( `shouldBe`
21 | Right
22 | [r|tasks: []
23 | vscode:
24 | extensions:
25 | - arrterian.nix-env-selector
26 | - jnoortheen.nix-ide
27 | |]
28 | )
29 |
--------------------------------------------------------------------------------
/test/Bootstrap/Data/Bootstrappable/VSCodeSettingsSpec.hs:
--------------------------------------------------------------------------------
1 | -- | Copyright : (c) Crown Copyright GCHQ
2 | module Bootstrap.Data.Bootstrappable.VSCodeSettingsSpec (spec) where
3 |
4 | import Bootstrap.Data.Bootstrappable (Bootstrappable (bootstrapContent))
5 | import Bootstrap.Data.Bootstrappable.VSCodeSettings (vsCodeSettingsFor)
6 | import Bootstrap.Data.DevContainer (DevContainerConfig (DevContainerConfig))
7 | import Test.Hspec (Spec, describe, it)
8 | import Test.Hspec.Expectations.Pretty (shouldBe)
9 |
10 | spec :: Spec
11 | spec = describe ".vscode/settings.json rendering" do
12 | it "renders the json correctly" do
13 | bootstrapContent (vsCodeSettingsFor (DevContainerConfig True))
14 | >>= ( `shouldBe`
15 | Right
16 | ( unlines
17 | ["{", " \"nixEnvSelector.nixFile\": \"${workspaceRoot}/flake.nix\",", " \"nixEnvSelector.useFlakes\": true", "}"]
18 | )
19 | )
20 |
--------------------------------------------------------------------------------
/hie.yaml:
--------------------------------------------------------------------------------
1 | # © Crown Copyright GCHQ
2 |
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 |
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | cradle:
15 | cabal:
16 | - path: "./src"
17 | component: "lib:nix-bootstrap"
18 |
19 | - path: "./app/Main.hs"
20 | component: "nix-bootstrap:exe:app"
21 |
22 | - path: "./app/Paths_nix_bootstrap.hs"
23 | component: "nix-bootstrap:exe:app"
24 |
25 | - path: "./test"
26 | component: "nix-bootstrap:test:nix-bootstrap-test"
27 |
--------------------------------------------------------------------------------
/test/Bootstrap/Data/Bootstrappable/Elm/IndexJsSpec.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | module Bootstrap.Data.Bootstrappable.Elm.IndexJsSpec (spec) where
5 |
6 | import Bootstrap.Data.Bootstrappable (Bootstrappable (bootstrapContent))
7 | import Bootstrap.Data.Bootstrappable.Elm.IndexJs (elmIndexJsFor)
8 | import Bootstrap.Data.ProjectType
9 | ( ElmMode (ElmModeNode),
10 | ElmOptions (ElmOptions),
11 | NodePackageManager (NPM),
12 | ProjectType (Elm),
13 | )
14 | import Test.Hspec (Spec, describe, it)
15 | import Test.Hspec.Expectations.Pretty (shouldBe)
16 | import Text.RawString.QQ (r)
17 |
18 | spec :: Spec
19 | spec = describe "index.js rendering" do
20 | it "renders the index.js correctly" do
21 | bootstrapContent (elmIndexJsFor . Elm $ ElmOptions (ElmModeNode NPM) True)
22 | >>= ( `shouldBe`
23 | Right
24 | [r|import { Elm } from "./Main.elm";
25 |
26 | Elm.Main.init({
27 | node: document.getElementById("root"),
28 | });
29 | |]
30 | )
31 |
--------------------------------------------------------------------------------
/test/Bootstrap/Nix/FlakeSpec.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | module Bootstrap.Nix.FlakeSpec (spec) where
5 |
6 | import Bootstrap.Data.ProjectName (mkProjectName)
7 | import Bootstrap.Data.Target (Target (TargetDefault))
8 | import Bootstrap.Nix.Expr (CommentsPolicy (ShowComments), writeExprFormatted)
9 | import Bootstrap.Nix.Flake (intermediateFlake)
10 | import qualified Relude.Unsafe as Unsafe
11 | import Test.Hspec (Spec, describe, it)
12 | import Test.Hspec.Expectations.Pretty (shouldBe)
13 | import Text.RawString.QQ (r)
14 |
15 | spec :: Spec
16 | spec = describe "intermediateFlake" do
17 | it "correctly writes the intermediate flake" do
18 | e <- writeExprFormatted ShowComments (intermediateFlake (Unsafe.fromJust $ mkProjectName "test-project") TargetDefault)
19 | e
20 | `shouldBe` Right
21 | [r|{
22 | description = "Development infrastructure for test-project";
23 | inputs = {
24 | nixpkgs-src.url = "nixpkgs/25.11";
25 | };
26 | outputs = _: {};
27 | }
28 | |]
29 |
--------------------------------------------------------------------------------
/src/Bootstrap/Nix/Expr/ReproducibleBuild/Rust.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | module Bootstrap.Nix.Expr.ReproducibleBuild.Rust (reproducibleRustBuild) where
5 |
6 | import Bootstrap.Nix.Expr
7 | ( Expr (ELetIn),
8 | nix,
9 | nixbinding,
10 | nixproperty,
11 | (|=),
12 | )
13 | import Bootstrap.Nix.Expr.ReproducibleBuild
14 | ( ReproducibleBuildExpr (ReproducibleBuildExpr),
15 | ReproducibleBuildRequirement (RBRNixpkgs),
16 | )
17 |
18 | reproducibleRustBuild ::
19 | -- | src path
20 | Expr ->
21 | ReproducibleBuildExpr
22 | reproducibleRustBuild srcDir =
23 | ReproducibleBuildExpr
24 | ( ELetIn
25 | ( ([nixproperty|src|] |= srcDir)
26 | :| [[nixbinding|cargoToml = builtins.fromTOML (builtins.readFile (src + "/Cargo.toml"));|]]
27 | )
28 | [nix|nixpkgs.rustPlatform.buildRustPackage {
29 | inherit src;
30 | inherit (cargoToml.package) name version;
31 | cargoLock.lockFile = src + "/Cargo.lock";
32 | }|]
33 | )
34 | (one RBRNixpkgs)
35 |
--------------------------------------------------------------------------------
/.devcontainer/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | # © Crown Copyright GCHQ
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | services:
15 | nix-bootstrap-dev-container:
16 | build:
17 | context: .
18 | dockerfile: Dockerfile
19 | # Override the default command so things don't shut down after the process ends.
20 | command: /bin/sh -c 'while sleep 1000; do :; done'
21 | container_name: nix-bootstrap-dev-container
22 | volumes:
23 | - ../:/workspaces/nix-bootstrap:cached
24 | - ${HOME}/.ssh:/home/vscode/.ssh
25 | version: "3"
26 |
--------------------------------------------------------------------------------
/src/Bootstrap/Data/Bootstrappable/Elm/IndexJs.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | module Bootstrap.Data.Bootstrappable.Elm.IndexJs
5 | ( ElmIndexJs,
6 | elmIndexJsFor,
7 | )
8 | where
9 |
10 | import Bootstrap.Data.Bootstrappable
11 | ( Bootstrappable (bootstrapContent, bootstrapName, bootstrapReason),
12 | )
13 | import Bootstrap.Data.ProjectType
14 | ( ElmMode (ElmModeNode),
15 | ElmOptions (ElmOptions),
16 | ProjectType (Elm),
17 | )
18 | import Text.RawString.QQ (r)
19 |
20 | data ElmIndexJs = ElmIndexJs
21 |
22 | instance Bootstrappable ElmIndexJs where
23 | bootstrapName = const "src/index.js"
24 | bootstrapReason = const "Inserts your Elm code into index.html"
25 | bootstrapContent ElmIndexJs =
26 | pure $
27 | Right
28 | [r|import { Elm } from "./Main.elm";
29 |
30 | Elm.Main.init({
31 | node: document.getElementById("root"),
32 | });
33 | |]
34 |
35 | elmIndexJsFor :: ProjectType -> Maybe ElmIndexJs
36 | elmIndexJsFor = \case
37 | Elm (ElmOptions (ElmModeNode _) _) -> Just ElmIndexJs
38 | _ -> Nothing
39 |
--------------------------------------------------------------------------------
/test/Bootstrap/Data/Bootstrappable/Rust/CargoLockSpec.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | module Bootstrap.Data.Bootstrappable.Rust.CargoLockSpec (spec) where
5 |
6 | import Bootstrap.Data.Bootstrappable (Bootstrappable (bootstrapContent))
7 | import Bootstrap.Data.Bootstrappable.Rust.CargoLock
8 | ( cargoLockFor,
9 | )
10 | import Bootstrap.Data.ProjectName (mkProjectName)
11 | import Bootstrap.Data.ProjectType
12 | ( ProjectType (Rust),
13 | )
14 | import qualified Relude.Unsafe as Unsafe
15 | import Test.Hspec (Spec, describe, it)
16 | import Test.Hspec.Expectations.Pretty (shouldBe)
17 | import Text.RawString.QQ (r)
18 |
19 | spec :: Spec
20 | spec = describe "Cargo.lock rendering" do
21 | it "renders correctly" do
22 | bootstrapContent (cargoLockFor Rust . Unsafe.fromJust $ mkProjectName "test-project")
23 | >>= ( `shouldBe`
24 | Right
25 | [r|# This file is automatically @generated by Cargo.
26 | # It is not intended for manual editing.
27 | version = 3
28 |
29 | [[package]]
30 | name = "test-project"
31 | version = "0.1.0"
32 | |]
33 | )
34 |
--------------------------------------------------------------------------------
/src/Bootstrap/Data/Version.hs:
--------------------------------------------------------------------------------
1 | -- | Copyright : (c) Crown Copyright GCHQ
2 | module Bootstrap.Data.Version (MajorVersion (MajorVersion), displayMajorVersion, parseMajorVersion, toMajorVersion) where
3 |
4 | import qualified Data.Version as V
5 | import Text.ParserCombinators.ReadP (readP_to_S)
6 |
7 | data MajorVersion = MajorVersion
8 | { flagshipVersion :: Int,
9 | majorVersion :: Int
10 | }
11 | deriving stock (Eq, Show)
12 |
13 | instance Ord MajorVersion where
14 | a <= b =
15 | flagshipVersion a < flagshipVersion b
16 | || (flagshipVersion a == flagshipVersion b && majorVersion a <= majorVersion b)
17 |
18 | displayMajorVersion :: (IsString s, Semigroup s) => MajorVersion -> s
19 | displayMajorVersion MajorVersion {..} = show flagshipVersion <> "." <> show majorVersion
20 |
21 | parseMajorVersion :: String -> Maybe MajorVersion
22 | parseMajorVersion = toMajorVersion <=< (fmap fst . viaNonEmpty last . readP_to_S V.parseVersion)
23 |
24 | toMajorVersion :: V.Version -> Maybe MajorVersion
25 | toMajorVersion version = do
26 | case V.versionBranch version of
27 | (flagship : major : _) -> Just $ MajorVersion flagship major
28 | _ -> Nothing
29 |
--------------------------------------------------------------------------------
/test/Bootstrap/Data/Bootstrappable/Rust/CargoTomlSpec.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | module Bootstrap.Data.Bootstrappable.Rust.CargoTomlSpec (spec) where
5 |
6 | import Bootstrap.Data.Bootstrappable (Bootstrappable (bootstrapContent))
7 | import Bootstrap.Data.Bootstrappable.Rust.CargoToml
8 | ( cargoTomlFor,
9 | )
10 | import Bootstrap.Data.ProjectName (mkProjectName)
11 | import Bootstrap.Data.ProjectType
12 | ( ProjectType (Rust),
13 | )
14 | import qualified Relude.Unsafe as Unsafe
15 | import Test.Hspec (Spec, describe, it)
16 | import Test.Hspec.Expectations.Pretty (shouldBe)
17 | import Text.RawString.QQ (r)
18 |
19 | spec :: Spec
20 | spec = describe "Cargo.toml rendering" do
21 | it "renders correctly" do
22 | bootstrapContent (cargoTomlFor Rust . Unsafe.fromJust $ mkProjectName "test-project")
23 | >>= ( `shouldBe`
24 | Right
25 | [r|[package]
26 | name = "test-project"
27 | version = "0.1.0"
28 | edition = "2021"
29 |
30 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
31 |
32 | [dependencies]
33 | |]
34 | )
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # © Crown Copyright GCHQ
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | # Direnv
16 | /.direnv
17 |
18 | # Haskell
19 | dist
20 | dist-*
21 | cabal-dev
22 | *.o
23 | *.hi
24 | *.hie
25 | *.chi
26 | *.chs.h
27 | *.dyn_o
28 | *.dyn_hi
29 | .hpc
30 | .hsenv
31 | .cabal-sandbox
32 | cabal.sandbox.config
33 | *.prof
34 | *.aux
35 | *.hp
36 | *.eventlog
37 | .stack-work/
38 | cabal.project.local
39 | cabal.project.local~
40 | .HTF/
41 | .ghc.environment.*
42 |
43 | # Miscellaneous
44 | *~
45 |
46 | # Nix
47 | result
48 | result-*
49 |
50 | # Pre-commit hooks
51 | .pre-commit-config.yaml
52 |
53 | # Releases
54 | /releases
55 |
56 |
--------------------------------------------------------------------------------
/src/Bootstrap/Nix/Expr/PreCommitHooks.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 | {-# LANGUAGE TemplateHaskellQuotes #-}
3 |
4 | -- | Copyright : (c) Crown Copyright GCHQ
5 | module Bootstrap.Nix.Expr.PreCommitHooks (ImportPreCommitHooksArgs (..), importPreCommitHooks) where
6 |
7 | import Bootstrap.Nix.Expr (Binding (BInherit), Expr (ESet), nix, nixident, (|*))
8 |
9 | data ImportPreCommitHooksArgs = ImportPreCommitHooksArgs
10 | { passNixpkgsThrough :: Bool,
11 | passSystemThrough :: Bool
12 | }
13 |
14 | -- | An expression which imports pre-commit hooks, assuming they're at nix/pre-commit-hooks.nix.
15 | --
16 | -- Passes pre-commit-hooks-lib and optionally nixpkgs and/or sustem through as arguments to the pre-commit hooks
17 | -- config - these must be in scope.
18 | importPreCommitHooks :: ImportPreCommitHooksArgs -> Expr
19 | importPreCommitHooks ImportPreCommitHooksArgs {..} =
20 | [nix|import nix/pre-commit-hooks.nix|]
21 | |* ESet
22 | False
23 | [ BInherit
24 | ( [nixident|pre-commit-hooks-lib|]
25 | :| [[nixident|nixpkgs|] | passNixpkgsThrough]
26 | <> [[nixident|system|] | passSystemThrough]
27 | )
28 | ]
29 |
--------------------------------------------------------------------------------
/src/Bootstrap/GitPod.hs:
--------------------------------------------------------------------------------
1 | -- | Description : Defines additional helpers used when nix-bootstrap is running in GitPod
2 | -- Copyright : (c) Crown Copyright GCHQ
3 | module Bootstrap.GitPod (resetPermissionsInGitPod) where
4 |
5 | import Bootstrap.Error
6 | ( CanDieOnError (dieOnError'),
7 | InProgressDuration (LongRunning),
8 | runWithProgressMsg,
9 | )
10 | import Bootstrap.Monad (MonadBootstrap)
11 | import Bootstrap.Terminal (putErrorLn)
12 | import Bootstrap.Unix (runCommand, whoami)
13 |
14 | -- | If running in GitPod, chowns the /nix directory to the gitpod
15 | -- user to prevent permissions errors.
16 | resetPermissionsInGitPod :: (MonadBootstrap m) => m ()
17 | resetPermissionsInGitPod =
18 | whoami >>= \case
19 | Right "gitpod" -> do
20 | void
21 | . dieOnError'
22 | . ExceptT
23 | . runWithProgressMsg LongRunning "Fixing GitPod's permissions on /nix"
24 | . ExceptT
25 | $ runCommand "/usr/bin/sudo" ["chown", "-R", "gitpod:gitpod", "/nix"]
26 | Right _ -> pass
27 | Left e -> do
28 | putErrorLn $ "Could not get current user: " <> toText (displayException e)
29 | putErrorLn "If running in GitPod, later steps may fail."
30 |
--------------------------------------------------------------------------------
/test/Bootstrap/Data/Bootstrappable/Haskell/LibHsSpec.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | module Bootstrap.Data.Bootstrappable.Haskell.LibHsSpec (spec) where
5 |
6 | import Bootstrap.Data.Bootstrappable (Bootstrappable (bootstrapContent))
7 | import Bootstrap.Data.Bootstrappable.Haskell.LibHs (libHsFor)
8 | import Bootstrap.Data.GHCVersion (GHCVersion (GHCVersion))
9 | import Bootstrap.Data.ProjectType
10 | ( HaskellOptions (HaskellOptions),
11 | HaskellProjectType (HaskellProjectTypeBasic),
12 | ProjectType (Haskell),
13 | SetUpHaskellBuild (SetUpHaskellBuild),
14 | )
15 | import Test.Hspec (Spec, describe, it)
16 | import Test.Hspec.Expectations.Pretty (shouldBe)
17 | import Text.RawString.QQ (r)
18 |
19 | spec :: Spec
20 | spec = describe "Lib.hs rendering" do
21 | it "renders correctly" do
22 | bootstrapContent (libHsFor . Haskell $ HaskellOptions (GHCVersion 9 0 2) (HaskellProjectTypeBasic $ SetUpHaskellBuild True))
23 | >>= ( `shouldBe`
24 | Right
25 | [r|module Lib (lib) where
26 |
27 | lib :: IO ()
28 | lib = error "todo: write the body of the lib function in src/Lib.hs"
29 | |]
30 | )
31 |
--------------------------------------------------------------------------------
/test/Bootstrap/Data/Bootstrappable/Haskell/PreludeHsSpec.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | module Bootstrap.Data.Bootstrappable.Haskell.PreludeHsSpec (spec) where
5 |
6 | import Bootstrap.Data.Bootstrappable (Bootstrappable (bootstrapContent))
7 | import Bootstrap.Data.Bootstrappable.Haskell.PreludeHs (preludeHsFor)
8 | import Bootstrap.Data.GHCVersion (GHCVersion (GHCVersion))
9 | import Bootstrap.Data.ProjectType
10 | ( HaskellOptions (HaskellOptions),
11 | HaskellProjectType (HaskellProjectTypeBasic),
12 | ProjectType (Haskell),
13 | SetUpHaskellBuild (SetUpHaskellBuild),
14 | )
15 | import Test.Hspec (Spec, describe, it)
16 | import Test.Hspec.Expectations.Pretty (shouldBe)
17 | import Text.RawString.QQ (r)
18 |
19 | spec :: Spec
20 | spec = describe "Prelude.hs rendering" do
21 | it "renders correctly" do
22 | bootstrapContent (preludeHsFor . Haskell $ HaskellOptions (GHCVersion 9 0 2) (HaskellProjectTypeBasic $ SetUpHaskellBuild True))
23 | >>= ( `shouldBe`
24 | Right
25 | [r|{-# OPTIONS_GHC -Wno-missing-import-lists #-}
26 |
27 | module Prelude (module Relude) where
28 |
29 | import Relude
30 | |]
31 | )
32 |
--------------------------------------------------------------------------------
/src/Bootstrap/Data/Bootstrappable/GitPodYml.hs:
--------------------------------------------------------------------------------
1 | -- | Copyright : (c) Crown Copyright GCHQ
2 | module Bootstrap.Data.Bootstrappable.GitPodYml (GitPodYml, gitPodYmlFor) where
3 |
4 | import Bootstrap.Data.Bootstrappable
5 | ( Bootstrappable
6 | ( bootstrapContent,
7 | bootstrapName,
8 | bootstrapReason
9 | ),
10 | bootstrapContentYaml,
11 | )
12 | import Bootstrap.Data.ProjectType (ProjectType)
13 | import Bootstrap.Data.VSCodeExtension (vsCodeExtensionsFor)
14 | import Data.Aeson (KeyValue ((.=)), ToJSON (toJSON))
15 | import qualified Data.Aeson as Aeson
16 | import Data.Aeson.Types (emptyArray)
17 |
18 | newtype GitPodYml = GitPodYml ProjectType
19 |
20 | instance Bootstrappable GitPodYml where
21 | bootstrapName = const ".gitpod.yml"
22 | bootstrapReason = const "This overrides GitPod's automated tasks; they are not needed."
23 | bootstrapContent = pure . Right . bootstrapContentYaml
24 |
25 | instance ToJSON GitPodYml where
26 | toJSON (GitPodYml projectType) =
27 | Aeson.object
28 | [ "tasks" .= emptyArray,
29 | "vscode" .= Aeson.object ["extensions" .= toJSON (vsCodeExtensionsFor projectType)]
30 | ]
31 |
32 | gitPodYmlFor :: ProjectType -> Maybe GitPodYml
33 | gitPodYmlFor = Just . GitPodYml
34 |
--------------------------------------------------------------------------------
/src/Bootstrap/Data/Bootstrappable/Rust/CargoLock.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 | {-# LANGUAGE ViewPatterns #-}
3 |
4 | -- | Copyright : (c) Crown Copyright GCHQ
5 | module Bootstrap.Data.Bootstrappable.Rust.CargoLock
6 | ( CargoLock,
7 | cargoLockFor,
8 | )
9 | where
10 |
11 | import Bootstrap.Data.Bootstrappable
12 | ( Bootstrappable (bootstrapContent, bootstrapName, bootstrapReason),
13 | )
14 | import Bootstrap.Data.ProjectName (ProjectName (unProjectName))
15 | import Bootstrap.Data.ProjectType
16 | ( ProjectType (Rust),
17 | )
18 | import Text.RawString.QQ (r)
19 |
20 | newtype CargoLock = CargoLock ProjectName
21 |
22 | instance Bootstrappable CargoLock where
23 | bootstrapName = const "Cargo.lock"
24 | bootstrapReason = const "The locked dependencies of your rust project"
25 | bootstrapContent (CargoLock (unProjectName -> n)) =
26 | pure . Right $
27 | [r|# This file is automatically @generated by Cargo.
28 | # It is not intended for manual editing.
29 | version = 3
30 |
31 | [[package]]
32 | name = "|]
33 | <> n
34 | <> [r|"
35 | version = "0.1.0"
36 | |]
37 |
38 | cargoLockFor :: ProjectType -> ProjectName -> Maybe CargoLock
39 | cargoLockFor = \case
40 | Rust -> Just . CargoLock
41 | _ -> const Nothing
42 |
--------------------------------------------------------------------------------
/src/Bootstrap/Data/Bootstrappable/Rust/CargoToml.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 | {-# LANGUAGE ViewPatterns #-}
3 |
4 | -- | Copyright : (c) Crown Copyright GCHQ
5 | module Bootstrap.Data.Bootstrappable.Rust.CargoToml
6 | ( CargoToml,
7 | cargoTomlFor,
8 | )
9 | where
10 |
11 | import Bootstrap.Data.Bootstrappable
12 | ( Bootstrappable (bootstrapContent, bootstrapName, bootstrapReason),
13 | )
14 | import Bootstrap.Data.ProjectName (ProjectName (unProjectName))
15 | import Bootstrap.Data.ProjectType
16 | ( ProjectType (Rust),
17 | )
18 | import Text.RawString.QQ (r)
19 |
20 | newtype CargoToml = CargoToml ProjectName
21 |
22 | instance Bootstrappable CargoToml where
23 | bootstrapName = const "Cargo.toml"
24 | bootstrapReason = const "The configuration of your rust project"
25 | bootstrapContent (CargoToml (unProjectName -> n)) =
26 | pure . Right $
27 | [r|[package]
28 | name = "|]
29 | <> n
30 | <> [r|"
31 | version = "0.1.0"
32 | edition = "2021"
33 |
34 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
35 |
36 | [dependencies]
37 | |]
38 |
39 | cargoTomlFor :: ProjectType -> ProjectName -> Maybe CargoToml
40 | cargoTomlFor = \case
41 | Rust -> Just . CargoToml
42 | _ -> const Nothing
43 |
--------------------------------------------------------------------------------
/nix/haskell-env.nix:
--------------------------------------------------------------------------------
1 | # © Crown Copyright GCHQ
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | {nixpkgs}: let
15 | # haskell package set
16 | baseHaskellPackages = nixpkgs.haskell.packages.ghc9102;
17 |
18 | # dev tools
19 | haskellEnv = baseHaskellPackages.ghcWithPackages (
20 | haskellPackages:
21 | with haskellPackages; [
22 | cabal-install
23 | haskell-language-server
24 | ]
25 | );
26 | in {
27 | inherit baseHaskellPackages;
28 | haskellDevTools = [
29 | haskellEnv
30 | # cabal uses wget but doesn't package it. This ensures a compatible version is used,
31 | # as devcontainers otherwise package the busybox version by default, which accepts
32 | # different arguments.
33 | nixpkgs.wget
34 | ];
35 | }
36 |
--------------------------------------------------------------------------------
/src/Bootstrap/Data/Bootstrappable/VSCodeSettings.hs:
--------------------------------------------------------------------------------
1 | -- | Copyright : (c) Crown Copyright GCHQ
2 | module Bootstrap.Data.Bootstrappable.VSCodeSettings (VSCodeSettings, vsCodeSettingsFor) where
3 |
4 | import Bootstrap.Data.Bootstrappable
5 | ( Bootstrappable (bootstrapContent, bootstrapName, bootstrapReason),
6 | bootstrapContentPrettyJson,
7 | )
8 | import Bootstrap.Data.DevContainer
9 | ( DevContainerConfig (DevContainerConfig),
10 | )
11 | import Data.Aeson (KeyValue ((.=)), ToJSON (toJSON))
12 | import qualified Data.Aeson as Aeson
13 |
14 | data VSCodeSettings = VSCodeSettings
15 |
16 | instance Bootstrappable VSCodeSettings where
17 | bootstrapName = const ".vscode/settings.json"
18 | bootstrapReason = const "This configures the settings for the extensions we recommend for VSCode."
19 | bootstrapContent = bootstrapContentPrettyJson []
20 |
21 | instance ToJSON VSCodeSettings where
22 | toJSON =
23 | const $
24 | Aeson.object
25 | [ "nixEnvSelector.nixFile" .= Aeson.String "${workspaceRoot}/flake.nix",
26 | "nixEnvSelector.useFlakes" .= Aeson.Bool True
27 | ]
28 |
29 | vsCodeSettingsFor :: DevContainerConfig -> Maybe VSCodeSettings
30 | vsCodeSettingsFor (DevContainerConfig True) = Just VSCodeSettings
31 | vsCodeSettingsFor (DevContainerConfig False) = Nothing
32 |
--------------------------------------------------------------------------------
/vulnerability-whitelist.toml:
--------------------------------------------------------------------------------
1 | # © Crown Copyright GCHQ
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | ["curl"]
16 | cve = ["CVE-2025-4947","CVE-2025-5025","CVE-2025-5399"]
17 | comment = "One vuln is a DOS; n-b is inherently not vulnerable. The others would require github to have been successfully attacked and proxied to replace nixpkgs; this is unlikely and beyond scope of mitigations possible here."
18 |
19 | ["gcc"]
20 | cve = ["CVE-2023-4039"]
21 | comment = "Reasonable worst-case is loss of availability; risk acceptable."
22 |
23 | ["glibc"]
24 | cve = ["CVE-2025-5702", "CVE-2025-5745"]
25 | comment = "Reasonable worst-case is loss of availability; risk acceptable."
26 |
27 | ["zlib-1.3.1"]
28 | cve = ["CVE-2023-6992"]
29 | comment = "We do not call the affected code with untrusted data."
30 |
--------------------------------------------------------------------------------
/.nix-bootstrap.dhall:
--------------------------------------------------------------------------------
1 | -- This file was generated by nix-bootstrap.
2 | -- It should be checked into version control.
3 | -- It is used to aid migration between nix-bootstrap versions and preserve idempotence.
4 | let NodePackageManager = < NPM | PNPm | Yarn >
5 |
6 | let ElmMode = < Bare | Node : NodePackageManager >
7 |
8 | let ElmOptions = { elmMode : ElmMode, provideElmReview : Bool }
9 |
10 | let HaskellProjectType = < ReplOnly | Basic : Bool | Server : Bool >
11 |
12 | let HaskellOptions =
13 | { ghcVersion : { major : Natural, minor : Natural, patch : Natural }
14 | , haskellProjectType : HaskellProjectType
15 | }
16 |
17 | let JavaOptions =
18 | { installMinishift : Bool
19 | , installLombok : Bool
20 | , setUpJavaBuild : < SetUpJavaBuild : Text | NoJavaBuild >
21 | , jdk : < OpenJDK | GraalVM >
22 | }
23 |
24 | let ProjectType =
25 | < Minimal
26 | | Elm : ElmOptions
27 | | Haskell : HaskellOptions
28 | | Node : NodePackageManager
29 | | Go : Bool
30 | | Java : JavaOptions
31 | | Python
32 | | Rust
33 | >
34 |
35 | in { projectName = "nix-bootstrap"
36 | , projectType = ProjectType.Minimal
37 | , setUpPreCommitHooks = True
38 | , setUpContinuousIntegration = True
39 | , setUpVSCodeDevContainer = True
40 | , target = {=}
41 | }
42 |
--------------------------------------------------------------------------------
/scripts/build-binary-cache.sh:
--------------------------------------------------------------------------------
1 | # shellcheck shell=bash
2 | #
3 | #
4 | #
5 | # © Crown Copyright GCHQ
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 |
19 | function runBuildBinaryCache() {
20 | # shellcheck disable=SC2154
21 | # (sed is defined in ../flake.nix)
22 | local sedCmd=$sed
23 | # shellcheck disable=SC2154
24 | # (tar is defined in ../flake.nix)
25 | local tarCmd=$tar
26 |
27 | set -e
28 | nix build
29 | # shellcheck disable=SC2046
30 | # (We want word splitting in the output from nix-store -qR)
31 | nix-store --export $(nix-store -qR "$(readlink result)") > nix-bootstrap-binary-cache
32 | $tarCmd cvzf "$(readlink result | $sedCmd 's:.*/::').tgz" nix-bootstrap-binary-cache
33 | mkdir -p releases
34 | mv ./*nix-bootstrap*.tgz releases
35 | rm -r nix-bootstrap-binary-cache
36 | }
37 |
38 | runBuildBinaryCache
39 |
40 |
--------------------------------------------------------------------------------
/src/Bootstrap/Data/Bootstrappable/VSCodeExtensions.hs:
--------------------------------------------------------------------------------
1 | -- |
2 | -- Description : Bootstraps .vscode/extensions.json
3 | -- Copyright : (c) Crown Copyright GCHQ
4 | module Bootstrap.Data.Bootstrappable.VSCodeExtensions
5 | ( VSCodeExtensions,
6 | vsCodeExtensionsFileFor,
7 | )
8 | where
9 |
10 | import Bootstrap.Data.Bootstrappable
11 | ( Bootstrappable (bootstrapContent, bootstrapName, bootstrapReason),
12 | bootstrapContentPrettyJson,
13 | )
14 | import Bootstrap.Data.ProjectType (ProjectType)
15 | import Bootstrap.Data.VSCodeExtension (vsCodeExtensionsFor)
16 | import Data.Aeson (KeyValue ((.=)), ToJSON (toJSON))
17 | import qualified Data.Aeson as Aeson
18 |
19 | -- | Represents the .vscode/extensions.json that we bootstrap
20 | newtype VSCodeExtensions = VSCodeExtensions ProjectType
21 |
22 | instance Bootstrappable VSCodeExtensions where
23 | bootstrapName = const ".vscode/extensions.json"
24 | bootstrapReason = const "This configures the extensions we recommend for VSCode."
25 | bootstrapContent = bootstrapContentPrettyJson []
26 |
27 | instance ToJSON VSCodeExtensions where
28 | toJSON (VSCodeExtensions projectType) =
29 | Aeson.object
30 | ["recommendations" .= Aeson.Array (fromList . (toJSON <$>) $ vsCodeExtensionsFor projectType)]
31 |
32 | -- | Constructs `VSCodeExtensions` for the given `ProjectType`
33 | vsCodeExtensionsFileFor :: ProjectType -> Maybe VSCodeExtensions
34 | vsCodeExtensionsFileFor = Just . VSCodeExtensions
35 |
--------------------------------------------------------------------------------
/test/Bootstrap/Data/Bootstrappable/VSCodeExtensionsSpec.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | module Bootstrap.Data.Bootstrappable.VSCodeExtensionsSpec (spec) where
5 |
6 | import Bootstrap.Data.Bootstrappable (Bootstrappable (bootstrapContent))
7 | import Bootstrap.Data.Bootstrappable.VSCodeExtensions (vsCodeExtensionsFileFor)
8 | import Bootstrap.Data.ProjectType
9 | ( InstallLombok (InstallLombok),
10 | InstallMinishift (InstallMinishift),
11 | JavaOptions (JavaOptions),
12 | JdkPackage (OpenJDK),
13 | ProjectType (Java),
14 | SetUpJavaBuild (NoJavaBuild),
15 | )
16 | import Test.Hspec (Spec, describe, it)
17 | import Test.Hspec.Expectations.Pretty (shouldBe)
18 | import Text.RawString.QQ (r)
19 |
20 | spec :: Spec
21 | spec = describe ".vscode/extensions.json rendering" do
22 | it "renders the json correctly" do
23 | bootstrapContent
24 | ( vsCodeExtensionsFileFor
25 | ( Java $
26 | JavaOptions
27 | (InstallMinishift True)
28 | (InstallLombok True)
29 | NoJavaBuild
30 | OpenJDK
31 | )
32 | )
33 | >>= ( `shouldBe`
34 | Right
35 | [r|{
36 | "recommendations": [
37 | "arrterian.nix-env-selector",
38 | "jnoortheen.nix-ide",
39 | "vscjava.vscode-java-pack",
40 | "gabrielbb.vscode-lombok"
41 | ]
42 | }
43 | |]
44 | )
45 |
--------------------------------------------------------------------------------
/test/Bootstrap/Data/ProjectNameSpec.hs:
--------------------------------------------------------------------------------
1 | -- | Copyright : (c) Crown Copyright GCHQ
2 | module Bootstrap.Data.ProjectNameSpec (spec) where
3 |
4 | import Bootstrap.Data.ProjectName
5 | ( ProjectName (unProjectName),
6 | mkProjectName,
7 | replaceSpacesWithDashes,
8 | tomlBiMap,
9 | )
10 | import qualified Relude.Unsafe as Unsafe
11 | import Test.Hspec (Spec, describe, it)
12 | import Test.Hspec.Expectations.Pretty (shouldBe)
13 | import qualified Toml
14 |
15 | spec :: Spec
16 | spec = describe "ProjectName" do
17 | describe "mkProjectName" do
18 | it "Does not remove spaces" do
19 | unProjectName (Unsafe.fromJust $ mkProjectName "my Project") `shouldBe` "my Project"
20 | describe "replaceSpacesWithDashes" do
21 | it "Replaces spaces with dashes" do
22 | replaceSpacesWithDashes <$> mkProjectName "my Project" `shouldBe` mkProjectName "my-Project"
23 | describe "tomlBiMap" do
24 | it "Converts valid project names bidirectionally" do
25 | let Toml.BiMap {..} = tomlBiMap
26 | projectName = Unsafe.fromJust $ mkProjectName "my-Project"
27 | forward projectName `shouldBe` Right (Toml.AnyValue (Toml.Text "my-Project"))
28 | backward (Toml.AnyValue (Toml.Text "my-Project")) `shouldBe` Right projectName
29 | it "Fails if the serialised project name is invalid" do
30 | let Toml.BiMap {..} = tomlBiMap
31 | backward (Toml.AnyValue (Toml.Text "1y-Project")) `shouldBe` Left (Toml.ArbitraryError "Invalid project name")
32 |
--------------------------------------------------------------------------------
/src/Bootstrap/Data/Bootstrappable/Elm/IndexHtml.hs:
--------------------------------------------------------------------------------
1 | -- | Copyright : (c) Crown Copyright GCHQ
2 | module Bootstrap.Data.Bootstrappable.Elm.IndexHtml
3 | ( ElmIndexHtml,
4 | elmIndexHtmlFor,
5 | )
6 | where
7 |
8 | import Bootstrap.Data.Bootstrappable
9 | ( Bootstrappable (bootstrapContent, bootstrapName, bootstrapReason),
10 | )
11 | import Bootstrap.Data.ProjectName (ProjectName (unProjectName))
12 | import Bootstrap.Data.ProjectType (ElmMode (ElmModeNode), ElmOptions (ElmOptions), ProjectType (Elm))
13 | import Text.Blaze.Html.Renderer.Pretty (renderHtml)
14 | import Text.Blaze.XHtml5 ((!))
15 | import qualified Text.Blaze.XHtml5 as H
16 | import qualified Text.Blaze.XHtml5.Attributes as HAttr
17 |
18 | newtype ElmIndexHtml = ElmIndexHtml ProjectName
19 |
20 | instance Bootstrappable ElmIndexHtml where
21 | bootstrapName = const "src/index.html"
22 | bootstrapReason = const "The top-level HTML file for your site"
23 | bootstrapContent (ElmIndexHtml projectName) =
24 | pure . Right . toText . renderHtml $ H.docTypeHtml do
25 | H.head do
26 | H.meta ! HAttr.charset "utf-8"
27 | H.title . H.toHtml $ unProjectName projectName
28 | H.script ! HAttr.type_ "module" ! HAttr.src "./index.js" $ mempty
29 | H.body $ H.div ! HAttr.id "root" $ mempty
30 |
31 | elmIndexHtmlFor :: ProjectName -> ProjectType -> Maybe ElmIndexHtml
32 | elmIndexHtmlFor projectName = \case
33 | Elm (ElmOptions (ElmModeNode _) _) -> Just $ ElmIndexHtml projectName
34 | _ -> Nothing
35 |
--------------------------------------------------------------------------------
/src/Bootstrap/Data/VSCodeExtension.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE GeneralisedNewtypeDeriving #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | -- Description : Utilities for working with VSCode extensions
5 | module Bootstrap.Data.VSCodeExtension (VSCodeExtension (..), vsCodeExtensionsFor) where
6 |
7 | import Bootstrap.Data.ProjectType
8 | ( HaskellOptions (HaskellOptions),
9 | InstallLombok (unInstallLombok),
10 | JavaOptions (JavaOptions),
11 | ProjectType
12 | ( Elm,
13 | Go,
14 | Haskell,
15 | Java,
16 | Minimal,
17 | Node,
18 | Python,
19 | Rust
20 | ),
21 | )
22 | import Data.Aeson (ToJSON)
23 |
24 | -- | Represents the ID of an individual extension
25 | newtype VSCodeExtension = VSCodeExtension Text
26 | deriving newtype (ToJSON)
27 |
28 | -- | The list of extensions we recommend for the given `ProjectType`
29 | vsCodeExtensionsFor :: ProjectType -> [VSCodeExtension]
30 | vsCodeExtensionsFor =
31 | (VSCodeExtension <$>) . (["arrterian.nix-env-selector", "jnoortheen.nix-ide"] <>) . \case
32 | Minimal -> []
33 | Elm _ -> ["Elmtooling.elm-ls-vscode"]
34 | (Haskell (HaskellOptions _ _)) -> ["haskell.haskell"]
35 | Node _ -> []
36 | Go _ -> ["golang.Go"]
37 | Java (JavaOptions _ installLombok _ _) ->
38 | ["vscjava.vscode-java-pack"]
39 | <> ["gabrielbb.vscode-lombok" | unInstallLombok installLombok]
40 | Python _ -> ["ms-python.python"]
41 | Rust -> ["rust-lang.rust-analyzer"]
42 |
--------------------------------------------------------------------------------
/test/Bootstrap/Data/Bootstrappable/Elm/ElmJsonSpec.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | module Bootstrap.Data.Bootstrappable.Elm.ElmJsonSpec (spec) where
5 |
6 | import Bootstrap.Data.Bootstrappable (Bootstrappable (bootstrapContent))
7 | import Bootstrap.Data.Bootstrappable.Elm.ElmJson (elmJsonFor)
8 | import Bootstrap.Data.ProjectType
9 | ( ElmMode (ElmModeBare),
10 | ElmOptions (ElmOptions),
11 | ProjectType (Elm),
12 | )
13 | import Test.Hspec (Spec, describe, it)
14 | import Test.Hspec.Expectations.Pretty (shouldBe)
15 | import Text.RawString.QQ (r)
16 |
17 | spec :: Spec
18 | spec = describe "elm.json rendering" do
19 | it "renders the elm.json correctly" do
20 | bootstrapContent (elmJsonFor . Elm $ ElmOptions ElmModeBare True)
21 | >>= ( `shouldBe`
22 | Right
23 | [r|{
24 | "type": "application",
25 | "source-directories": [
26 | "src"
27 | ],
28 | "elm-version": "0.19.1",
29 | "dependencies": {
30 | "direct": {
31 | "elm/browser": "1.0.2",
32 | "elm/core": "1.0.5",
33 | "elm/html": "1.0.0"
34 | },
35 | "indirect": {
36 | "elm/json": "1.1.3",
37 | "elm/time": "1.0.0",
38 | "elm/url": "1.0.0",
39 | "elm/virtual-dom": "1.0.3"
40 | }
41 | },
42 | "test-dependencies": {
43 | "direct": {},
44 | "indirect": {}
45 | }
46 | }
47 | |]
48 | )
49 |
--------------------------------------------------------------------------------
/test/Bootstrap/Data/Bootstrappable/Elm/IndexHtmlSpec.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | module Bootstrap.Data.Bootstrappable.Elm.IndexHtmlSpec (spec) where
5 |
6 | import Bootstrap.Data.Bootstrappable (Bootstrappable (bootstrapContent))
7 | import Bootstrap.Data.Bootstrappable.Elm.IndexHtml
8 | ( elmIndexHtmlFor,
9 | )
10 | import Bootstrap.Data.ProjectName (mkProjectName)
11 | import Bootstrap.Data.ProjectType
12 | ( ElmMode (ElmModeNode),
13 | ElmOptions (ElmOptions),
14 | NodePackageManager (NPM),
15 | ProjectType (Elm),
16 | )
17 | import qualified Relude.Unsafe as Unsafe
18 | import Test.Hspec (Spec, describe, it)
19 | import Test.Hspec.Expectations.Pretty (shouldBe)
20 | import Text.RawString.QQ (r)
21 |
22 | spec :: Spec
23 | spec = describe "index.html rendering" do
24 | it "renders the index.html correctly" do
25 | bootstrapContent
26 | ( elmIndexHtmlFor (Unsafe.fromJust $ mkProjectName "test-project")
27 | . Elm
28 | $ ElmOptions (ElmModeNode NPM) True
29 | )
30 | >>= ( `shouldBe`
31 | Right
32 | [r|
33 |
34 |
35 |
36 |
37 |
38 | test-project
39 |
40 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | |]
49 | )
50 |
--------------------------------------------------------------------------------
/nix/release.nix:
--------------------------------------------------------------------------------
1 | # © Crown Copyright GCHQ
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | {
15 | baseHaskellPackages,
16 | nixpkgs,
17 | }: let
18 | src = ../.;
19 | nix-bootstrap-unstripped = (nixpkgs.haskell.lib.overrideCabal (baseHaskellPackages.callCabal2nix "nix-bootstrap" src {})
20 | (
21 | _: {
22 | configureFlags = ["-fprod"];
23 | }
24 | ))
25 | .overrideAttrs (_: prev: {buildInputs = (prev.buildInputs or []) ++ [nixpkgs.alejandra];});
26 | in {
27 | nix-bootstrap = nixpkgs.stdenv.mkDerivation {
28 | inherit src;
29 | inherit (nix-bootstrap-unstripped) name version;
30 | buildInputs = with nixpkgs; [
31 | alejandra
32 | glibc
33 | gmp
34 | libffi
35 | ncurses
36 | zlib
37 | ];
38 | installPhase = ''
39 | mkdir -p $out/bin
40 | cp ${(nixpkgs.haskell.lib.enableSeparateBinOutput nix-bootstrap-unstripped).bin}/bin/app $out/bin/nix-bootstrap
41 | '';
42 | };
43 | }
44 |
--------------------------------------------------------------------------------
/src/Bootstrap/Data/Bootstrappable/Elm/MainElm.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 |
3 | module Bootstrap.Data.Bootstrappable.Elm.MainElm (MainElm, mainElmFor) where
4 |
5 | import Bootstrap.Data.Bootstrappable
6 | ( Bootstrappable
7 | ( bootstrapContent,
8 | bootstrapName,
9 | bootstrapReason
10 | ),
11 | )
12 | import Bootstrap.Data.ProjectType (ProjectType (Elm))
13 | import Text.RawString.QQ (r)
14 |
15 | data MainElm = MainElm
16 |
17 | instance Bootstrappable MainElm where
18 | bootstrapName = const "src/Main.elm"
19 | bootstrapReason = const "The entrypoint for your Elm code"
20 | bootstrapContent =
21 | const . pure $
22 | Right
23 | [r|module Main exposing (Model, Msg, main)
24 |
25 | import Browser
26 | import Html exposing (Html, button, text)
27 | import Html.Events exposing (onClick)
28 |
29 |
30 | main : Program () Model Msg
31 | main =
32 | Browser.sandbox
33 | { init = init
34 | , update = update
35 | , view = view
36 | }
37 |
38 |
39 | type alias Model =
40 | ()
41 |
42 |
43 | init : Model
44 | init =
45 | ()
46 |
47 |
48 | type Msg
49 | = NoOp
50 |
51 |
52 | update : Msg -> Model -> Model
53 | update msg model =
54 | case msg of
55 | NoOp ->
56 | model
57 |
58 |
59 | view : Model -> Html Msg
60 | view () =
61 | button [ onClick NoOp ] [ text "Hello, world!" ]
62 | |]
63 |
64 | mainElmFor :: ProjectType -> Maybe MainElm
65 | mainElmFor = \case
66 | Elm _ -> Just MainElm
67 | _ -> Nothing
68 |
--------------------------------------------------------------------------------
/src/Bootstrap/Data/Bootstrappable/Go/Modfile.hs:
--------------------------------------------------------------------------------
1 | -- | Copyright : (c) Crown Copyright GCHQ
2 | module Bootstrap.Data.Bootstrappable.Go.Modfile (GoModfile (GoModfile), goModfileFor) where
3 |
4 | import Bootstrap.Data.Bootstrappable (Bootstrappable (bootstrapContent, bootstrapName, bootstrapReason))
5 | import Bootstrap.Data.ProjectName (ProjectName (unProjectName))
6 | import Bootstrap.Error (CanDieOnError (dieOnErrorWithPrefix))
7 | import Bootstrap.Monad (MonadBootstrap)
8 | import Bootstrap.Nix.Evaluate
9 | ( NixBinaryPaths,
10 | getVersionOfNixpkgsAttribute,
11 | )
12 | import Text.Regex (mkRegex, subRegex)
13 |
14 | data GoModfile = GoModfile {goModfileProjectName :: ProjectName, goModfileGoVersion :: Text}
15 |
16 | instance Bootstrappable GoModfile where
17 | bootstrapName = const "go.mod"
18 | bootstrapReason = const "The declaration of your Go module and its dependencies"
19 | bootstrapContent GoModfile {..} =
20 | pure . Right $
21 | unlines
22 | [ "module " <> unProjectName goModfileProjectName,
23 | "",
24 | "go " <> goModfileGoVersion
25 | ]
26 |
27 | goModfileFor :: (MonadBootstrap m) => NixBinaryPaths -> ProjectName -> m GoModfile
28 | goModfileFor nixBinaryPaths projectName = do
29 | goVersion <-
30 | dieOnErrorWithPrefix "Could not get bootstrapped Go version"
31 | . ExceptT
32 | $ getVersionOfNixpkgsAttribute nixBinaryPaths "go"
33 | let majorMinor = toText $ subRegex (mkRegex "^([0-9]+\\.[0-9]+).*") goVersion "\\1"
34 | pure $ GoModfile projectName majorMinor
35 |
--------------------------------------------------------------------------------
/.github/workflows/build.yaml:
--------------------------------------------------------------------------------
1 | # © Crown Copyright GCHQ
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | name: Build
15 | on:
16 | pull_request:
17 |
18 | jobs:
19 | build-nix-bootstrap:
20 | name: Build nix-bootstrap
21 | runs-on: ubuntu-latest
22 | steps:
23 | - uses: actions/checkout@v3
24 | with:
25 | fetch-depth: 0
26 | - uses: cachix/install-nix-action@v18
27 | with:
28 | nix_path: "nixpkgs=channel:nixpkgs-unstable"
29 | - name: Check version number has been updated
30 | run: >
31 | (git fetch --all && git diff "$(git describe --tags --abbrev=0)" -- package.yaml | grep version) ||
32 | (echo "You must bump the nix-bootstrap version number in package.yaml!" && exit 1)
33 | - name: Run pre-commit hooks
34 | run: nix flake check
35 | - name: Build nix-bootstrap
36 | run: nix build
37 | - name: Check for vulnerabilities
38 | run: nix run .\#ciPackages_vulnix -- -C -w vulnerability-whitelist.toml result/
39 |
--------------------------------------------------------------------------------
/test/Bootstrap/Data/Bootstrappable/Elm/MainElmSpec.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | module Bootstrap.Data.Bootstrappable.Elm.MainElmSpec (spec) where
5 |
6 | import Bootstrap.Data.Bootstrappable (Bootstrappable (bootstrapContent))
7 | import Bootstrap.Data.Bootstrappable.Elm.MainElm (mainElmFor)
8 | import Bootstrap.Data.ProjectType
9 | ( ElmMode (ElmModeBare),
10 | ElmOptions (ElmOptions),
11 | ProjectType (Elm),
12 | )
13 | import Test.Hspec (Spec, describe, it)
14 | import Test.Hspec.Expectations.Pretty (shouldBe)
15 | import Text.RawString.QQ (r)
16 |
17 | spec :: Spec
18 | spec = describe "Main.elm rendering" do
19 | it "renders the Main.elm correctly" do
20 | bootstrapContent (mainElmFor . Elm $ ElmOptions ElmModeBare True)
21 | >>= ( `shouldBe`
22 | Right
23 | [r|module Main exposing (Model, Msg, main)
24 |
25 | import Browser
26 | import Html exposing (Html, button, text)
27 | import Html.Events exposing (onClick)
28 |
29 |
30 | main : Program () Model Msg
31 | main =
32 | Browser.sandbox
33 | { init = init
34 | , update = update
35 | , view = view
36 | }
37 |
38 |
39 | type alias Model =
40 | ()
41 |
42 |
43 | init : Model
44 | init =
45 | ()
46 |
47 |
48 | type Msg
49 | = NoOp
50 |
51 |
52 | update : Msg -> Model -> Model
53 | update msg model =
54 | case msg of
55 | NoOp ->
56 | model
57 |
58 |
59 | view : Model -> Html Msg
60 | view () =
61 | button [ onClick NoOp ] [ text "Hello, world!" ]
62 | |]
63 | )
64 |
--------------------------------------------------------------------------------
/src/Bootstrap/Data/Bootstrappable/HaskellPackagesNix.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 |
3 | -- |
4 | -- Copyright : (c) Crown Copyright GCHQ
5 | -- Description : A file defining a haskell package set
6 | module Bootstrap.Data.Bootstrappable.HaskellPackagesNix (HaskellPackagesNix, haskellPackagesNixFor) where
7 |
8 | import Bootstrap.Data.Bootstrappable
9 | ( Bootstrappable (bootstrapContent, bootstrapName, bootstrapReason),
10 | bootstrapContentNix,
11 | )
12 | import Bootstrap.Data.ProjectType (HaskellOptions, ProjectType (Haskell))
13 | import Bootstrap.Nix.Expr (Expr (EFunc), FunctionArgs (FASet), IsNixExpr (toNixExpr), nixident)
14 | import Bootstrap.Nix.Expr.Haskell (haskellPackagesExpr)
15 |
16 | -- | A separate nix file defining the haskell package set
17 | newtype HaskellPackagesNix = HaskellPackagesNix HaskellOptions
18 | deriving stock (Eq, Show)
19 |
20 | instance Bootstrappable HaskellPackagesNix where
21 | bootstrapName = const "nix/haskell-packages.nix"
22 | bootstrapReason = const "This configures the haskell package set from which your dependencies will be pulled."
23 | bootstrapContent = bootstrapContentNix
24 |
25 | instance IsNixExpr HaskellPackagesNix where
26 | toNixExpr (HaskellPackagesNix opts) =
27 | EFunc
28 | (FASet $ one [nixident|nixpkgs|])
29 | (haskellPackagesExpr opts)
30 |
31 | -- | Gives a `HaskellPackagesNix` (or `Nothing`) as appropriate for the project details
32 | -- given.
33 | haskellPackagesNixFor :: ProjectType -> Maybe HaskellPackagesNix
34 | haskellPackagesNixFor (Haskell haskellOptions) = Just $ HaskellPackagesNix haskellOptions
35 | haskellPackagesNixFor _ = Nothing
36 |
--------------------------------------------------------------------------------
/src/Bootstrap/Data/Bootstrappable/Elm/ElmJson.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | module Bootstrap.Data.Bootstrappable.Elm.ElmJson
5 | ( ElmJson,
6 | elmJsonFor,
7 | )
8 | where
9 |
10 | import Bootstrap.Data.Bootstrappable
11 | ( Bootstrappable (bootstrapContent, bootstrapName, bootstrapReason),
12 | bootstrapContentPrettyJson,
13 | )
14 | import Bootstrap.Data.ProjectType (ProjectType (Elm))
15 | import Data.Aeson (ToJSON (toJSON))
16 | import Data.Aeson.QQ.Simple (aesonQQ)
17 |
18 | data ElmJson = ElmJson
19 |
20 | instance Bootstrappable ElmJson where
21 | bootstrapName = const "elm.json"
22 | bootstrapReason = const "The configuration of your elm project"
23 | bootstrapContent = bootstrapContentPrettyJson ["type", "source-directories", "elm-version"]
24 |
25 | instance ToJSON ElmJson where
26 | toJSON ElmJson =
27 | [aesonQQ|{
28 | "type": "application",
29 | "source-directories": [
30 | "src"
31 | ],
32 | "elm-version": "0.19.1",
33 | "dependencies": {
34 | "direct": {
35 | "elm/browser": "1.0.2",
36 | "elm/core": "1.0.5",
37 | "elm/html": "1.0.0"
38 | },
39 | "indirect": {
40 | "elm/json": "1.1.3",
41 | "elm/time": "1.0.0",
42 | "elm/url": "1.0.0",
43 | "elm/virtual-dom": "1.0.3"
44 | }
45 | },
46 | "test-dependencies": {
47 | "direct": {},
48 | "indirect": {}
49 | }
50 | }
51 | |]
52 |
53 | elmJsonFor :: ProjectType -> Maybe ElmJson
54 | elmJsonFor = \case
55 | Elm _ -> Just ElmJson
56 | _ -> Nothing
57 |
--------------------------------------------------------------------------------
/src/Bootstrap/Data/Bootstrappable/SystemsNix.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 |
3 | -- |
4 | -- Copyright : (c) Crown Copyright GCHQ
5 | -- Description : The code for nix/systems.nix
6 | module Bootstrap.Data.Bootstrappable.SystemsNix (SystemsNix (SystemsNix)) where
7 |
8 | import Bootstrap.Data.Bootstrappable
9 | ( Bootstrappable (bootstrapContent, bootstrapName, bootstrapReason),
10 | bootstrapContentNix,
11 | )
12 | import Bootstrap.Nix.Expr (IsNixExpr (toNixExpr), nix)
13 |
14 | -- | A separate nix file defining functions for working with multiple systems easily,
15 | -- based on numtide/flake-utils
16 | data SystemsNix = SystemsNix
17 | deriving stock (Eq, Show)
18 |
19 | instance Bootstrappable SystemsNix where
20 | bootstrapName = const "nix/systems.nix"
21 | bootstrapReason = const "This contains helpers for working with different systems."
22 | bootstrapContent = bootstrapContentNix
23 |
24 | instance IsNixExpr SystemsNix where
25 | toNixExpr SystemsNix =
26 | [nix|# A minimal subset of numtide/flake-utils
27 | {
28 | forEachSystem = systems: f:
29 | builtins.foldl' (
30 | attrs: system: let
31 | ret = f system;
32 | in
33 | builtins.foldl' (attrs: key:
34 | attrs //
35 | {
36 | ${key} =
37 | (attrs.${key} or {})
38 | // {
39 | ${system} = ret.${key};
40 | };
41 | })
42 | attrs (builtins.attrNames ret)) {}
43 | systems;
44 |
45 | system = allSystems:
46 | builtins.listToAttrs
47 | (builtins.map (system: {
48 | name = system;
49 | value = system;
50 | })
51 | allSystems);
52 | }|]
53 |
--------------------------------------------------------------------------------
/src/Bootstrap/Nix/Expr/Python.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 | {-# LANGUAGE TemplateHaskellQuotes #-}
3 |
4 | -- | Copyright : (c) Crown Copyright GCHQ
5 | module Bootstrap.Nix.Expr.Python (machNixLegacyNixBinding, machNixFlakeInput, pythonPackagesBinding) where
6 |
7 | import Bootstrap.Nix.Expr
8 | ( Binding,
9 | Expr (EGrouping, ELit, ESet),
10 | Literal (LString),
11 | nix,
12 | nixbinding,
13 | nixproperty,
14 | (|*),
15 | (|=),
16 | )
17 |
18 | machNixVersion :: Text
19 | machNixVersion = "3.5.0"
20 |
21 | -- | A binding which pulls mach-nix using builtins.fetchGit
22 | machNixLegacyNixBinding :: Binding
23 | machNixLegacyNixBinding =
24 | [nixproperty|mach-nix|]
25 | |= [nix|import|]
26 | |* EGrouping
27 | ( [nix|builtins.fetchGit|]
28 | |* ESet
29 | False
30 | [ [nixbinding|url = "https://github.com/DavHau/mach-nix";|],
31 | [nixproperty|ref|] |= ELit (LString $ "refs/tags/" <> machNixVersion)
32 | ]
33 | )
34 | |* [nix|{}|]
35 |
36 | -- | A binding to be used as an input in a flake to pull mach-nix
37 | machNixFlakeInput :: Binding
38 | machNixFlakeInput =
39 | [nixproperty|mach-nix.url|]
40 | |= ELit (LString $ "github:DavHau/mach-nix?ref=" <> machNixVersion)
41 |
42 | -- | A binding which provides python packages based on requirements.txt.
43 | --
44 | -- It expects mach-nix any system to be in scope.
45 | pythonPackagesBinding ::
46 | Binding
47 | pythonPackagesBinding =
48 | [nixbinding|pythonPackages = mach-nix.lib.${system}.mkPython (rec {
49 | requirements = builtins.readFile ./requirements.txt;
50 | python = "python39";
51 | });|]
52 |
--------------------------------------------------------------------------------
/test/Bootstrap/Data/Bootstrappable/Haskell/MainHsSpec.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | module Bootstrap.Data.Bootstrappable.Haskell.MainHsSpec (spec) where
5 |
6 | import Bootstrap.Data.Bootstrappable (Bootstrappable (bootstrapContent))
7 | import Bootstrap.Data.Bootstrappable.Haskell.MainHs (mainHsFor)
8 | import Bootstrap.Data.GHCVersion (GHCVersion (GHCVersion))
9 | import Bootstrap.Data.ProjectType
10 | ( HaskellOptions (HaskellOptions),
11 | HaskellProjectType (HaskellProjectTypeBasic, HaskellProjectTypeServer),
12 | ProjectType (Haskell),
13 | SetUpHaskellBuild (SetUpHaskellBuild),
14 | )
15 | import Test.Hspec (Spec, describe, it)
16 | import Test.Hspec.Expectations.Pretty (shouldBe)
17 | import Text.RawString.QQ (r)
18 |
19 | spec :: Spec
20 | spec = describe "Main.hs rendering" do
21 | it "renders correctly for a simple library" do
22 | bootstrapContent (mainHsFor . Haskell $ HaskellOptions (GHCVersion 9 0 2) (HaskellProjectTypeBasic $ SetUpHaskellBuild True))
23 | >>= ( `shouldBe`
24 | Right
25 | [r|module Main (main) where
26 |
27 | import Lib (lib)
28 |
29 | main :: IO ()
30 | main = lib
31 | |]
32 | )
33 | it "renders correctly for a WAI app served by warp" do
34 | bootstrapContent (mainHsFor . Haskell $ HaskellOptions (GHCVersion 9 0 2) (HaskellProjectTypeServer $ SetUpHaskellBuild True))
35 | >>= ( `shouldBe`
36 | Right
37 | [r|module Main (main) where
38 |
39 | import Network.Wai.Handler.Warp (run)
40 | import Server (app)
41 |
42 | main :: IO ()
43 | main = do {putTextLn "Serving on 8080..."; run 8080 app}
44 | |]
45 | )
46 |
--------------------------------------------------------------------------------
/src/Bootstrap/Nix/Expr/ReproducibleBuild.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | -- Description : Definitions for reproducible build expressions
5 | module Bootstrap.Nix.Expr.ReproducibleBuild
6 | ( ReproducibleBuildExpr (..),
7 | ReproducibleBuildRequirement (..),
8 | reproducibleBuildRequirementIdentifier,
9 | sortRbeRequirements,
10 | )
11 | where
12 |
13 | import Bootstrap.Nix.Expr (Expr, Identifier, nixident)
14 | import qualified Data.List.NonEmpty as NE
15 |
16 | -- | A representation of an identifier the reproducible build expression expects to be in scope
17 | data ReproducibleBuildRequirement
18 | = RBRHaskellPackages
19 | | RBRNixpkgs
20 | deriving stock (Eq, Show)
21 |
22 | reproducibleBuildRequirementIdentifier :: ReproducibleBuildRequirement -> Identifier
23 | reproducibleBuildRequirementIdentifier = \case
24 | RBRHaskellPackages -> [nixident|haskellPackages|]
25 | RBRNixpkgs -> [nixident|nixpkgs|]
26 |
27 | -- | An expression defining the reproducible build for some software
28 | data ReproducibleBuildExpr = ReproducibleBuildExpr
29 | { rbeExpr :: Expr,
30 | -- | What identifiers the expression expects to be in scope
31 | rbeRequirements :: NonEmpty ReproducibleBuildRequirement
32 | }
33 | deriving stock (Eq, Show)
34 |
35 | -- | Ensures requirements with dependencies on each other are properly ordered in a let-in style,
36 | -- such that the dependencies are defined before the things that depend on them.
37 | sortRbeRequirements :: NonEmpty ReproducibleBuildRequirement -> NonEmpty ReproducibleBuildRequirement
38 | sortRbeRequirements = NE.sortBy f
39 | where
40 | f RBRNixpkgs _ = LT
41 | f _ RBRNixpkgs = GT
42 | f _ _ = EQ
43 |
--------------------------------------------------------------------------------
/src/Bootstrap/Data/Bootstrappable/Haskell/PreludeHs.hs:
--------------------------------------------------------------------------------
1 | -- | Copyright : (c) Crown Copyright GCHQ
2 | module Bootstrap.Data.Bootstrappable.Haskell.PreludeHs
3 | ( PreludeHs,
4 | preludeHsFor,
5 | )
6 | where
7 |
8 | import Bootstrap.Data.Bootstrappable
9 | ( Bootstrappable (bootstrapContent, bootstrapName, bootstrapReason),
10 | HaskellImport (HaskellImportAll),
11 | bootstrapContentHaskell,
12 | haskellModule,
13 | haskellModuleImports,
14 | haskellModulePragmas,
15 | )
16 | import Bootstrap.Data.ProjectType
17 | ( HaskellOptions (HaskellOptions),
18 | HaskellProjectType (HaskellProjectTypeBasic, HaskellProjectTypeReplOnly, HaskellProjectTypeServer),
19 | ProjectType (Haskell),
20 | )
21 | import Control.Lens ((?~))
22 | import Language.Haskell.TH.Syntax (ModName (ModName))
23 |
24 | data PreludeHs = PreludeHs
25 |
26 | instance Bootstrappable PreludeHs where
27 | bootstrapName = const "src/Prelude.hs"
28 | bootstrapReason = const "The haskell prelude - what this exports is implicitly imported into every other module"
29 | bootstrapContent PreludeHs =
30 | pure . pure . bootstrapContentHaskell $
31 | haskellModule (ModName "Prelude") (one "module Relude")
32 | & haskellModulePragmas
33 | ?~ one "{-# OPTIONS_GHC -Wno-missing-import-lists #-}"
34 | & haskellModuleImports
35 | ?~ one (HaskellImportAll (ModName "Relude"))
36 |
37 | preludeHsFor :: ProjectType -> Maybe PreludeHs
38 | preludeHsFor = \case
39 | Haskell (HaskellOptions _ haskellProjectType) -> case haskellProjectType of
40 | HaskellProjectTypeReplOnly -> Nothing
41 | HaskellProjectTypeBasic _ -> Just PreludeHs
42 | HaskellProjectTypeServer _ -> Just PreludeHs
43 | _ -> Nothing
44 |
--------------------------------------------------------------------------------
/src/Bootstrap/Nix/Expr/ReproducibleBuild/Go.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 | {-# LANGUAGE TemplateHaskellQuotes #-}
3 |
4 | -- | Copyright : (c) Crown Copyright GCHQ
5 | module Bootstrap.Nix.Expr.ReproducibleBuild.Go (reproducibleGoBuild) where
6 |
7 | import Bootstrap.Data.ProjectName (ProjectName (unProjectName))
8 | import Bootstrap.Nix.Expr
9 | ( Binding (BLineComment),
10 | CommentsPolicy (ShowComments),
11 | Expr (ELit, ESet),
12 | Literal (LString),
13 | nix,
14 | nixbinding,
15 | nixproperty,
16 | writeBinding,
17 | (|*),
18 | (|=),
19 | )
20 | import Bootstrap.Nix.Expr.ReproducibleBuild
21 | ( ReproducibleBuildExpr (ReproducibleBuildExpr),
22 | ReproducibleBuildRequirement (RBRNixpkgs),
23 | )
24 |
25 | -- | Produces a reproducible build for a Golang project.
26 | reproducibleGoBuild :: ProjectName -> ReproducibleBuildExpr
27 | reproducibleGoBuild projectName =
28 | ReproducibleBuildExpr
29 | ( [nix|nixpkgs.buildGoModule|]
30 | |* ESet
31 | False
32 | [ [nixproperty|pname|] |= ELit (LString $ unProjectName projectName),
33 | [nixbinding|version = "0.1.0";|],
34 | [nixbinding|src = ./.;|],
35 | [nixbinding|vendorSha256 = null;|],
36 | [nixbinding|# Swap out the line above for the one below once you start adding dependencies.
37 | |],
38 | [nixbinding|# After your dependencies change, builds will fail until you update the hash below.
39 | |],
40 | [nixbinding|# When the build fails, it will tell you what the expected hash is.
41 | |],
42 | BLineComment (writeBinding ShowComments [nixbinding|vendorSha256 = "sha256-00000000000000000000000000000000000000000000";|])
43 | ]
44 | )
45 | (one RBRNixpkgs)
46 |
--------------------------------------------------------------------------------
/test/Bootstrap/Data/Bootstrappable/Elm/PackageJsonSpec.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | module Bootstrap.Data.Bootstrappable.Elm.PackageJsonSpec (spec) where
5 |
6 | import Bootstrap.Data.Bootstrappable (Bootstrappable (bootstrapContent))
7 | import Bootstrap.Data.Bootstrappable.Elm.PackageJson (elmPackageJsonFor)
8 | import Bootstrap.Data.ProjectType
9 | ( ElmMode (ElmModeNode),
10 | ElmOptions (ElmOptions),
11 | NodePackageManager (NPM),
12 | ProjectType (Elm),
13 | )
14 | import Test.Hspec (Spec, describe, it)
15 | import Test.Hspec.Expectations.Pretty (shouldBe)
16 | import Text.RawString.QQ (r)
17 |
18 | spec :: Spec
19 | spec = describe "package.json rendering" do
20 | it "renders the package.json correctly" do
21 | bootstrapContent (elmPackageJsonFor . Elm $ ElmOptions (ElmModeNode NPM) True)
22 | >>= ( `shouldBe`
23 | Right
24 | [r|{
25 | "dependencies": {
26 | "buffer": "^5.5.0",
27 | "process": "^0.11.10",
28 | "punycode": "^1.4.1",
29 | "querystring-es3": "^0.2.1",
30 | "url": "^0.11.0"
31 | },
32 | "devDependencies": {
33 | "@babel/core": ">=7.13.0 <8.0.0",
34 | "@babel/preset-env": "^7.1.6",
35 | "@parcel/core": ">=2.8.3 <3.0.0",
36 | "@parcel/transformer-elm": "^2.8.3",
37 | "elm": "^0.19.1-5",
38 | "parcel": "^2.8.3"
39 | },
40 | "peerDependencies": {
41 | "@babel/core": ">=7.13.0 <8.0.0",
42 | "@babel/preset-env": "^7.1.6",
43 | "@parcel/core": ">=2.8.3 <3.0.0",
44 | "elm": "^0.19.1-5"
45 | },
46 | "scripts": {
47 | "build": "NODE_ENV=production parcel build",
48 | "dev": "NODE_ENV=development parcel"
49 | },
50 | "source": "src/index.html"
51 | }
52 | |]
53 | )
54 |
--------------------------------------------------------------------------------
/.envrc:
--------------------------------------------------------------------------------
1 | # © Crown Copyright GCHQ
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | direnv version 2.23.0 || exit 1
15 | if [ $(nix-env --version | grep -oE '[0-9]+\.[0-9]+' | head -n1 | sed 's/\./000/') -lt 20004 ]; then
16 | echo 'This project is set up to work with Nix Flakes, which your version of nix doesn'"'"'t support.'
17 | echo 'Please upgrade your nix version to at least 2.4 to continue.'
18 | exit 1
19 | fi
20 | if ! nix show-config --extra-experimental-features nix-command | grep experimental-features | grep flakes 1>/dev/null 2>&1; then
21 | printf '\033[31m'
22 | echo 'This project is set up to work with Nix Flakes, which you don'"'"'t currently have enabled.'
23 | echo 'Please enable flakes by following the instructions at https://nixos.wiki/wiki/flakes#Installing_flakes'
24 | printf '\033[0m'
25 | exit 1
26 | fi
27 | if ! nix show-config 1>/dev/null 2>&1; then
28 | printf '\033[31m'
29 | echo 'This project is set up to work with Nix Flakes, which you don'"'"'t currently have enabled.'
30 | echo 'Specifically, the "nix-command" option is missing from your nix experimental-features configuration.'
31 | echo 'Please enable flakes by following the instructions at https://nixos.wiki/wiki/flakes#Installing_flakes'
32 | printf '\033[0m'
33 | exit 1
34 | fi
35 | use flake
36 | eval "$shellHook"
37 |
--------------------------------------------------------------------------------
/src/Bootstrap/Data/Bootstrappable/Haskell/LibHs.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE TemplateHaskellQuotes #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | module Bootstrap.Data.Bootstrappable.Haskell.LibHs
5 | ( LibHs,
6 | libHsFor,
7 | )
8 | where
9 |
10 | import Bootstrap.Data.Bootstrappable
11 | ( Bootstrappable (bootstrapContent, bootstrapName, bootstrapReason),
12 | bootstrapContentHaskell,
13 | haskellModule,
14 | haskellModuleDecs,
15 | )
16 | import Bootstrap.Data.ProjectType
17 | ( HaskellOptions (HaskellOptions),
18 | HaskellProjectType (HaskellProjectTypeBasic, HaskellProjectTypeReplOnly, HaskellProjectTypeServer),
19 | ProjectType (Haskell),
20 | )
21 | import Control.Lens ((?~))
22 | import Language.Haskell.TH (conT, varE)
23 | import Language.Haskell.TH.Syntax
24 | ( Body (NormalB),
25 | Clause (Clause),
26 | Dec (FunD, SigD),
27 | ModName (ModName),
28 | mkName,
29 | )
30 |
31 | data LibHs = LibHs
32 |
33 | instance Bootstrappable LibHs where
34 | bootstrapName = const "src/Lib.hs"
35 | bootstrapReason = const "The entrypoint of your haskell library"
36 | bootstrapContent LibHs = do
37 | let lib = mkName "lib"
38 | ioUnit <- [t|$(conT $ mkName "IO") ()|]
39 | errorLine <- [|$(varE $ mkName "error") "todo: write the body of the lib function in src/Lib.hs"|]
40 | pure . pure . bootstrapContentHaskell $
41 | haskellModule (ModName "Lib") (one "lib")
42 | & haskellModuleDecs
43 | ?~ SigD lib ioUnit
44 | :| [FunD lib [Clause [] (NormalB errorLine) []]]
45 |
46 | libHsFor :: ProjectType -> Maybe LibHs
47 | libHsFor = \case
48 | Haskell (HaskellOptions _ haskellProjectType) -> case haskellProjectType of
49 | HaskellProjectTypeReplOnly -> Nothing
50 | HaskellProjectTypeBasic _ -> Just LibHs
51 | HaskellProjectTypeServer _ -> Nothing
52 | _ -> Nothing
53 |
--------------------------------------------------------------------------------
/src/Bootstrap/Data/ProjectName.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE GeneralisedNewtypeDeriving #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | module Bootstrap.Data.ProjectName
5 | ( ProjectName (unProjectName),
6 | mkProjectName,
7 | replaceSpacesWithDashes,
8 | tomlBiMap,
9 | )
10 | where
11 |
12 | import qualified Data.Char as C
13 | import qualified Data.Text as T
14 | import Dhall (FromDhall, ToDhall)
15 | import qualified Toml
16 |
17 | -- | The name of a project. It can be comprised of characters matching [-_a-zA-Z0-9 ]
18 | newtype ProjectName = ProjectName {unProjectName :: Text}
19 | deriving stock (Eq, Show)
20 | deriving newtype (FromDhall, ToDhall)
21 |
22 | -- | A smart constructor which gives back Nothing if the project name
23 | -- contains invalid characters or begins with a non-alphabetical character.
24 | mkProjectName :: Text -> Maybe ProjectName
25 | mkProjectName name =
26 | if T.all C.isAlpha (T.take 1 name) && T.all isValidChar name
27 | then Just $ ProjectName name
28 | else Nothing
29 | where
30 | isValidChar :: Char -> Bool
31 | isValidChar = \case
32 | '_' -> True
33 | '-' -> True
34 | ' ' -> True
35 | c -> C.isAlphaNum c
36 |
37 | replaceSpacesWithDashes :: ProjectName -> ProjectName
38 | replaceSpacesWithDashes = ProjectName . T.replace " " "-" . unProjectName
39 |
40 | tomlBiMap :: Toml.TomlBiMap ProjectName Toml.AnyValue
41 | tomlBiMap = Toml.BiMap {forward, backward}
42 | where
43 | forward :: ProjectName -> Either Toml.TomlBiMapError Toml.AnyValue
44 | forward projectName = Right . Toml.AnyValue . Toml.Text $ unProjectName projectName
45 | backward :: Toml.AnyValue -> Either Toml.TomlBiMapError ProjectName
46 | backward (Toml.AnyValue v) = case v of
47 | Toml.Text t ->
48 | maybeToRight
49 | (Toml.ArbitraryError "Invalid project name")
50 | (mkProjectName t)
51 | _ -> first Toml.WrongValue $ Toml.mkMatchError Toml.TText v
52 |
--------------------------------------------------------------------------------
/test/Bootstrap/Data/Bootstrappable/GitignoreSpec.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | module Bootstrap.Data.Bootstrappable.GitignoreSpec (spec) where
5 |
6 | import Bootstrap.Data.Bootstrappable (Bootstrappable (bootstrapContent))
7 | import Bootstrap.Data.Bootstrappable.Gitignore (gitignoreFor)
8 | import Bootstrap.Data.PreCommitHook (PreCommitHooksConfig (PreCommitHooksConfig))
9 | import Bootstrap.Data.ProjectType
10 | ( NodePackageManager (Yarn),
11 | ProjectType (Go, Node),
12 | SetUpGoBuild (SetUpGoBuild),
13 | )
14 | import Test.Hspec (Spec, describe, it)
15 | import Test.Hspec.Expectations.Pretty (shouldBe)
16 | import Text.RawString.QQ (r)
17 |
18 | spec :: Spec
19 | spec = describe ".gitignore rendering" do
20 | it "renders blocks correctly with go and flakes, without pre-commit hooks" do
21 | bootstrapContent (gitignoreFor (Go $ SetUpGoBuild False) (PreCommitHooksConfig False))
22 | >>= ( `shouldBe`
23 | Right
24 | [r|# Binaries for programs and plugins
25 | *.exe
26 | *.exe~
27 | *.dll
28 | *.so
29 | *.dylib
30 |
31 | # Direnv Config
32 | /.direnv
33 |
34 | # Go workspace file
35 | go.work
36 |
37 | # Nix Artefacts
38 | result
39 | result-*
40 |
41 | # OS-Specific
42 | .DS_Store
43 | *~
44 |
45 | # Output of go coverage tool
46 | *.out
47 |
48 | # Test binary, built with `go test -c`
49 | *.test
50 | |]
51 | )
52 |
53 | it "renders blocks correctly with yarn and pre-commit hooks" do
54 | bootstrapContent (gitignoreFor (Node Yarn) (PreCommitHooksConfig True))
55 | >>= ( `shouldBe`
56 | Right
57 | [r|# Direnv Config
58 | /.direnv
59 |
60 | # Nix Artefacts
61 | result
62 | result-*
63 |
64 | # Node
65 | /node_modules
66 |
67 | # OS-Specific
68 | .DS_Store
69 | *~
70 |
71 | # Pre-Commit Hooks
72 | /.pre-commit-config.yaml
73 |
74 | # Yarn error log
75 | yarn-error.log
76 | |]
77 | )
78 |
--------------------------------------------------------------------------------
/test/Bootstrap/Data/ProjectTypeSpec.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE TypeApplications #-}
2 | {-# OPTIONS_GHC -Wno-orphans #-}
3 |
4 | -- | Copyright : (c) Crown Copyright GCHQ
5 | module Bootstrap.Data.ProjectTypeSpec (spec) where
6 |
7 | import Bootstrap.Data.ProjectType
8 | ( ArtefactId (ArtefactId),
9 | ElmMode (ElmModeBare, ElmModeNode),
10 | ElmOptions (ElmOptions),
11 | JavaOptions (JavaOptions),
12 | ProjectType (Elm, Go, Java, Minimal, Node, Python),
13 | SetUpJavaBuild (NoJavaBuild, SetUpJavaBuild),
14 | )
15 | import Data.Char (isAsciiLower, isAsciiUpper)
16 | import Test.Hspec (Spec, describe)
17 | import Test.Hspec.QuickCheck (prop)
18 | import Test.QuickCheck
19 | ( Arbitrary (arbitrary),
20 | arbitraryBoundedEnum,
21 | choose,
22 | oneof,
23 | suchThat,
24 | vectorOf,
25 | )
26 | import Test.Util (dhallRoundtripTest)
27 |
28 | instance Arbitrary ProjectType where
29 | arbitrary =
30 | oneof
31 | [ pure Minimal,
32 | Elm
33 | <$> ( ElmOptions
34 | <$> oneof [pure ElmModeBare, ElmModeNode <$> arbitraryBoundedEnum]
35 | <*> arbitrary
36 | ),
37 | Node <$> arbitraryBoundedEnum,
38 | Go <$> arbitraryBoundedEnum,
39 | Java <$> (JavaOptions <$> arbitraryBoundedEnum <*> arbitraryBoundedEnum <*> arbitrary <*> arbitraryBoundedEnum),
40 | Python <$> arbitraryBoundedEnum
41 | ]
42 |
43 | instance Arbitrary SetUpJavaBuild where
44 | arbitrary =
45 | oneof
46 | [ SetUpJavaBuild <$> arbitrary,
47 | pure NoJavaBuild
48 | ]
49 |
50 | instance Arbitrary ArtefactId where
51 | arbitrary = do
52 | l <- choose (1, 10)
53 | artefactIdChars <- vectorOf l (arbitrary `suchThat` \c -> isAsciiUpper c || isAsciiLower c)
54 | pure . ArtefactId $ toText artefactIdChars
55 |
56 | spec :: Spec
57 | spec = describe "ProjectType" do
58 | prop "roundtrips to Dhall" (dhallRoundtripTest @ProjectType)
59 |
--------------------------------------------------------------------------------
/src/Bootstrap/Data/Bootstrappable/Elm/PackageJson.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | module Bootstrap.Data.Bootstrappable.Elm.PackageJson
5 | ( ElmPackageJson,
6 | elmPackageJsonFor,
7 | )
8 | where
9 |
10 | import Bootstrap.Data.Bootstrappable
11 | ( Bootstrappable (bootstrapContent, bootstrapName, bootstrapReason),
12 | bootstrapContentPrettyJson,
13 | )
14 | import Bootstrap.Data.ProjectType (ElmMode (ElmModeNode), ElmOptions (ElmOptions), ProjectType (Elm))
15 | import Data.Aeson (ToJSON (toJSON))
16 | import Data.Aeson.QQ.Simple (aesonQQ)
17 |
18 | data ElmPackageJson = ElmPackageJson
19 |
20 | instance Bootstrappable ElmPackageJson where
21 | bootstrapName = const "package.json"
22 | bootstrapReason = const "The node configuration of your Elm project"
23 | bootstrapContent = bootstrapContentPrettyJson []
24 |
25 | instance ToJSON ElmPackageJson where
26 | toJSON ElmPackageJson =
27 | [aesonQQ|{
28 | "devDependencies": {
29 | "@babel/core": ">=7.13.0 <8.0.0",
30 | "@babel/preset-env": "^7.1.6",
31 | "@parcel/core": ">=2.8.3 <3.0.0",
32 | "@parcel/transformer-elm": "^2.8.3",
33 | "elm": "^0.19.1-5",
34 | "parcel": "^2.8.3"
35 | },
36 | "scripts": {
37 | "dev": "NODE_ENV=development parcel",
38 | "build": "NODE_ENV=production parcel build"
39 | },
40 | "source": "src/index.html",
41 | "peerDependencies": {
42 | "@babel/core": ">=7.13.0 <8.0.0",
43 | "@babel/preset-env": "^7.1.6",
44 | "@parcel/core": ">=2.8.3 <3.0.0",
45 | "elm": "^0.19.1-5"
46 | },
47 | "dependencies": {
48 | "buffer": "^5.5.0",
49 | "process": "^0.11.10",
50 | "punycode": "^1.4.1",
51 | "querystring-es3": "^0.2.1",
52 | "url": "^0.11.0"
53 | }
54 | }
55 | |]
56 |
57 | elmPackageJsonFor :: ProjectType -> Maybe ElmPackageJson
58 | elmPackageJsonFor = \case
59 | Elm (ElmOptions (ElmModeNode _) _) -> Just ElmPackageJson
60 | _ -> Nothing
61 |
--------------------------------------------------------------------------------
/test/Bootstrap/Nix/Expr/MkShellSpec.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | module Bootstrap.Nix.Expr.MkShellSpec (spec) where
5 |
6 | import Bootstrap.Data.PreCommitHook
7 | ( PreCommitHooksConfig (PreCommitHooksConfig),
8 | )
9 | import Bootstrap.Data.ProjectType
10 | ( ProjectType (Minimal, Rust),
11 | )
12 | import Bootstrap.Nix.Expr (nix)
13 | import Bootstrap.Nix.Expr.MkShell
14 | ( BuildInputSpec (BuildInputSpec),
15 | mkShell,
16 | )
17 | import Test.Hspec (Spec, describe, it)
18 | import Test.Hspec.Expectations.Pretty (shouldBe)
19 |
20 | spec :: Spec
21 | spec = do
22 | describe "mkShell" do
23 | it "gives no shell hook when one isn't needed" do
24 | mkShell (BuildInputSpec [] [] (PreCommitHooksConfig False) Minimal [])
25 | `shouldBe` [nix|nixpkgs.mkShell {
26 | buildInputs = [
27 | # Insert any dependencies that should exist in the dev shell environment here
28 | ];
29 | }|]
30 | it "gives a proper shell hook for projects with pre-commit hooks" do
31 | mkShell (BuildInputSpec [] [] (PreCommitHooksConfig True) Minimal [])
32 | `shouldBe` [nix|nixpkgs.mkShell {
33 | buildInputs = preCommitHooks.tools;
34 | inherit (preCommitHooks.allHooks) shellHook;
35 | }|]
36 | it "gives a proper shell hook for Rust projects" do
37 | mkShell (BuildInputSpec [] [] (PreCommitHooksConfig False) Rust [])
38 | `shouldBe` [nix|nixpkgs.mkShell {
39 | buildInputs = [
40 | # Insert any dependencies that should exist in the dev shell environment here
41 | ];
42 | shellHook = ''
43 | export RUST_SRC_PATH=${nixpkgs.rustPlatform.rustLibSrc}
44 | '';
45 | }|]
46 | it "gives a proper shell hook for Rust projects with pre-commit hooks" do
47 | mkShell (BuildInputSpec [] [] (PreCommitHooksConfig True) Rust [])
48 | `shouldBe` [nix|nixpkgs.mkShell {
49 | buildInputs = preCommitHooks.tools;
50 | shellHook = ''
51 | export RUST_SRC_PATH=${nixpkgs.rustPlatform.rustLibSrc}
52 | ${preCommitHooks.allHooks.shellHook}
53 | '';
54 | }|]
55 |
--------------------------------------------------------------------------------
/src/Bootstrap/Data/Config.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE DataKinds #-}
2 | {-# LANGUAGE TemplateHaskell #-}
3 |
4 | -- |
5 | -- Description : Utilities for working with nix-bootstrap's configuration
6 | -- Copyright : (c) Crown Copyright GCHQ
7 | module Bootstrap.Data.Config
8 | ( -- * Config Data Types
9 | Config,
10 |
11 | -- * Loading an existing `Config`
12 | LoadConfigResult (..),
13 | loadConfig,
14 |
15 | -- * Creating a `Config` from scratch
16 | configFor,
17 |
18 | -- * Accessing `Config` values
19 | configProjectName,
20 | configProjectType,
21 | configSetUpPreCommitHooks,
22 | configSetUpContinuousIntegration,
23 | configSetUpVSCodeDevContainer,
24 | configTarget,
25 |
26 | -- * Exceptions
27 | NonFlakeConfigException,
28 | )
29 | where
30 |
31 | import Bootstrap.Data.Config.Internal
32 | ( Config,
33 | ConfigV11 (ConfigV11),
34 | LoadConfigResult
35 | ( LoadConfigResultError,
36 | LoadConfigResultFound,
37 | LoadConfigResultNotFound
38 | ),
39 | NonFlakeConfigException,
40 | VersionedConfig (VersionedConfigV11),
41 | VersionedProjectType (VPT11),
42 | configV11ProjectName,
43 | configV11ProjectType,
44 | configV11SetUpContinuousIntegration,
45 | configV11SetUpPreCommitHooks,
46 | configV11SetUpVSCodeDevContainer,
47 | configV11Target,
48 | loadConfig,
49 | _Current,
50 | )
51 | import Bootstrap.Data.Config.Internal.TH (makeConfigLenses)
52 | import Bootstrap.Data.ContinuousIntegration
53 | ( ContinuousIntegrationConfig,
54 | )
55 | import Bootstrap.Data.DevContainer (DevContainerConfig)
56 | import Bootstrap.Data.PreCommitHook (PreCommitHooksConfig)
57 | import Bootstrap.Data.ProjectName (ProjectName)
58 | import Bootstrap.Data.ProjectType (ProjectType)
59 | import Bootstrap.Data.Target (Target)
60 |
61 | makeConfigLenses 'ConfigV11
62 |
63 | -- | Initialise a new `Config` from scratch
64 | configFor ::
65 | ProjectName ->
66 | ProjectType ->
67 | PreCommitHooksConfig ->
68 | ContinuousIntegrationConfig ->
69 | DevContainerConfig ->
70 | Target ->
71 | Config
72 | configFor a1 a2 a3 a4 a5 a6 =
73 | VersionedConfigV11 $
74 | ConfigV11 a1 (VPT11 a2) a3 a4 a5 a6
75 |
--------------------------------------------------------------------------------
/src/Bootstrap/Error.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE FlexibleInstances #-}
2 | {-# LANGUAGE ScopedTypeVariables #-}
3 | {-# LANGUAGE UndecidableInstances #-}
4 |
5 | -- | Copyright : (c) Crown Copyright GCHQ
6 | module Bootstrap.Error
7 | ( InProgressDuration (..),
8 | runWithProgressMsg,
9 | CanDieOnError (dieOnError, dieOnError', dieOnErrorWithPrefix),
10 | )
11 | where
12 |
13 | import Bootstrap.Monad (MonadBootstrap)
14 | import Bootstrap.Terminal (putErrorLn, withAttributes)
15 | import System.Terminal
16 | ( MonadColorPrinter (cyan, foreground, green, red),
17 | MonadFormattingPrinter (bold, italic),
18 | MonadPrinter (putText, putTextLn),
19 | )
20 |
21 | data InProgressDuration = Quick | LongRunning
22 |
23 | runWithProgressMsg ::
24 | forall e m a.
25 | (MonadBootstrap m) =>
26 | InProgressDuration ->
27 | Text ->
28 | ExceptT e m a ->
29 | m (Either e a)
30 | runWithProgressMsg duration msg action = do
31 | withAttributes [italic, foreground cyan] . putText $ msg <> msgEnder
32 | res <- runExceptT action
33 | case res of
34 | Left _ ->
35 | withAttributes [bold, foreground red] $ putTextLn "ERROR"
36 | Right _ ->
37 | withAttributes [bold, foreground green] $ putTextLn "DONE"
38 | pure res
39 | where
40 | msgEnder :: Text
41 | msgEnder = case duration of
42 | Quick -> "... "
43 | LongRunning -> " (this may take a while)... "
44 |
45 | class CanDieOnError m where
46 | dieOnError :: (e -> Text) -> ExceptT e m a -> m a
47 |
48 | -- | Convenience function to print exceptions unmodified when dying
49 | dieOnError' :: (Exception e) => ExceptT e m a -> m a
50 | dieOnError' = dieOnError (toText . displayException)
51 |
52 | -- | Convenience function to print exceptions unmodified but with a prefix when dying.
53 | --
54 | -- Adds a colon and space to the end of the prefix.
55 | dieOnErrorWithPrefix :: (Exception e) => Text -> ExceptT e m a -> m a
56 | dieOnErrorWithPrefix prefix = dieOnError (((prefix <> ": ") <>) . toText . displayException)
57 |
58 | instance (MonadBootstrap m) => CanDieOnError m where
59 | dieOnError displayError action =
60 | runExceptT action >>= \case
61 | Left e -> putErrorLn (displayError e) >> exitFailure
62 | Right a -> pure a
63 |
--------------------------------------------------------------------------------
/test/Bootstrap/Data/Bootstrappable/Haskell/ServerHsSpec.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | module Bootstrap.Data.Bootstrappable.Haskell.ServerHsSpec (spec) where
5 |
6 | import Bootstrap.Data.Bootstrappable (Bootstrappable (bootstrapContent))
7 | import Bootstrap.Data.Bootstrappable.Haskell.ServerHs (serverHsFor)
8 | import Bootstrap.Data.GHCVersion (GHCVersion (GHCVersion))
9 | import Bootstrap.Data.ProjectType
10 | ( HaskellOptions (HaskellOptions),
11 | HaskellProjectType (HaskellProjectTypeServer),
12 | ProjectType (Haskell),
13 | SetUpHaskellBuild (SetUpHaskellBuild),
14 | )
15 | import Test.Hspec (Spec, describe, it)
16 | import Test.Hspec.Expectations.Pretty (shouldBe)
17 | import Text.RawString.QQ (r)
18 |
19 | spec :: Spec
20 | spec = describe "Server.hs rendering" do
21 | it "renders correctly" do
22 | bootstrapContent (serverHsFor . Haskell $ HaskellOptions (GHCVersion 9 0 2) (HaskellProjectTypeServer $ SetUpHaskellBuild True))
23 | >>= ( `shouldBe`
24 | Right
25 | [r|{-# LANGUAGE DataKinds #-}
26 | {-# LANGUAGE TemplateHaskell #-}
27 | {-# LANGUAGE TypeApplications #-}
28 | {-# LANGUAGE TypeOperators #-}
29 |
30 | module Server (app) where
31 |
32 | import Data.Aeson (defaultOptions)
33 | import Data.Aeson.TH (deriveJSON)
34 | import Servant (Application, Get, Handler, JSON, NoContent (NoContent), PutNoContent, ReqBody, Server, serve, type (:<|>) ((:<|>)), type (:>))
35 |
36 | type API = "user" :> (ListUsersEndpoint :<|> PutUserEndpoint)
37 | type ListUsersEndpoint = Get '[JSON] [User]
38 | type PutUserEndpoint = ReqBody '[JSON] User :> PutNoContent
39 | data User = User {name :: String, age :: Int} deriving stock Show
40 |
41 | deriveJSON defaultOptions ''User
42 |
43 | exampleUsers :: [User]
44 | exampleUsers = [User "A. Example" 46, User "A. N. Other" 51]
45 | app :: Application
46 | app = serve (Proxy @API) server
47 | server :: Server API
48 | server = listUsersHandler :<|> putUserHandler
49 | listUsersHandler :: Handler [User]
50 | listUsersHandler = pure exampleUsers
51 | putUserHandler :: User -> Handler NoContent
52 | putUserHandler user = do {putTextLn ("I would insert the following user into the DB if this were real: " <> show user);
53 | pure NoContent}
54 | |]
55 | )
56 |
--------------------------------------------------------------------------------
/test/Bootstrap/StateSpec.hs:
--------------------------------------------------------------------------------
1 | -- | Copyright : (c) Crown Copyright GCHQ
2 | module Bootstrap.StateSpec (spec) where
3 |
4 | import Bootstrap.State
5 | ( InputLine (InputLine),
6 | TextInputState (TextInputState),
7 | backspaceInputLine,
8 | handleBackspacePress,
9 | handleCharEntry,
10 | inputLineLength,
11 | insertIntoInputLine,
12 | updateCursorPos,
13 | )
14 | import Test.Hspec (Spec, describe, it)
15 | import Test.Hspec.Expectations.Pretty (shouldBe)
16 |
17 | spec :: Spec
18 | spec = do
19 | describe "InputLine" do
20 | describe "insertIntoInputLine" do
21 | it "inserts into the inputLine at the expected position" $
22 | insertIntoInputLine (InputLine "hit") 2 'n' `shouldBe` InputLine "hint"
23 | describe "backspaceInputLine" do
24 | it "deletes from the inputLine at the expected position" $
25 | backspaceInputLine (InputLine "hint") 3 `shouldBe` InputLine "hit"
26 | describe "inputLineLength" do
27 | it "gives the correct length" $
28 | inputLineLength (InputLine "hint") `shouldBe` 4
29 | describe "TextInputState" do
30 | describe "updateCursorPos" do
31 | it "correctly applies the update function" $
32 | updateCursorPos (TextInputState 3 (InputLine "abcdefg")) (+ 3) `shouldBe` TextInputState 6 (InputLine "abcdefg")
33 | it "cannot move left of zero" $
34 | updateCursorPos (TextInputState 3 (InputLine "abcdefg")) (\v -> v - 5) `shouldBe` TextInputState 0 (InputLine "abcdefg")
35 | it "cannot move right of the end of the string" $
36 | updateCursorPos (TextInputState 3 (InputLine "abcdefg")) (+ 20) `shouldBe` TextInputState 7 (InputLine "abcdefg")
37 | describe "handleCharEntry" do
38 | it "inserts a character and updates the cursor position" $
39 | handleCharEntry (TextInputState 3 (InputLine "abcdefg")) '!' `shouldBe` TextInputState 4 (InputLine "abc!defg")
40 | describe "handleBackspacePress" do
41 | it "deletes a character and updates the cursor position" $
42 | handleBackspacePress (TextInputState 3 (InputLine "abcdefg")) `shouldBe` TextInputState 2 (InputLine "abdefg")
43 | it "does not update the state when the cursor position is 0" $
44 | handleBackspacePress (TextInputState 0 (InputLine "abcdefg")) `shouldBe` TextInputState 0 (InputLine "abcdefg")
45 |
--------------------------------------------------------------------------------
/src/Bootstrap/Data/Bootstrappable/Envrc.hs:
--------------------------------------------------------------------------------
1 | -- | Copyright : (c) Crown Copyright GCHQ
2 | module Bootstrap.Data.Bootstrappable.Envrc (Envrc (Envrc)) where
3 |
4 | import Bootstrap.Data.Bootstrappable (Bootstrappable (bootstrapContent, bootstrapName, bootstrapReason))
5 | import Bootstrap.Data.PreCommitHook (PreCommitHooksConfig, unPreCommitHooksConfig)
6 |
7 | newtype Envrc = Envrc {preCommitHooksConfig :: PreCommitHooksConfig}
8 |
9 | instance Bootstrappable Envrc where
10 | bootstrapName = const ".envrc"
11 | bootstrapReason Envrc {preCommitHooksConfig} =
12 | "This tells direnv to load the nix shell"
13 | <> if unPreCommitHooksConfig preCommitHooksConfig
14 | then " and set up the pre-commit hooks."
15 | else "."
16 | bootstrapContent Envrc {preCommitHooksConfig} =
17 | pure . Right . unlines $
18 | [ "direnv version 2.23.0 || exit 1",
19 | "if [ $(nix-env --version | grep -oE '[0-9]+\\.[0-9]+' | head -n1 | sed 's/\\./000/') -lt 20004 ]; then",
20 | " echo 'This project is set up to work with Nix Flakes, which your version of nix doesn'\"'\"'t support.'",
21 | " echo 'Please upgrade your nix version to at least 2.4 to continue.'",
22 | " exit 1",
23 | "fi",
24 | "if ! nix show-config --extra-experimental-features nix-command | grep experimental-features | grep flakes 1>/dev/null 2>&1; then",
25 | " printf '\\033[31m'",
26 | " echo 'This project is set up to work with Nix Flakes, which you don'\"'\"'t currently have enabled.'",
27 | " echo 'Please enable flakes by following the instructions at https://nixos.wiki/wiki/flakes#Installing_flakes'",
28 | " printf '\\033[0m'",
29 | " exit 1",
30 | "fi",
31 | "if ! nix show-config 1>/dev/null 2>&1; then",
32 | " printf '\\033[31m'",
33 | " echo 'This project is set up to work with Nix Flakes, which you don'\"'\"'t currently have enabled.'",
34 | " echo 'Specifically, the \"nix-command\" option is missing from your nix experimental-features configuration.'",
35 | " echo 'Please enable flakes by following the instructions at https://nixos.wiki/wiki/flakes#Installing_flakes'",
36 | " printf '\\033[0m'",
37 | " exit 1",
38 | "fi",
39 | "",
40 | "use flake"
41 | ]
42 | <> ["eval \"$shellHook\"" | unPreCommitHooksConfig preCommitHooksConfig]
43 |
--------------------------------------------------------------------------------
/src/Bootstrap/Nix/Expr/Haskell.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | -- Description : Nix expressions specific to haskell projects
5 | module Bootstrap.Nix.Expr.Haskell (haskellPackagesExpr) where
6 |
7 | import Bootstrap.Data.GHCVersion (ghcVersionProperty)
8 | import Bootstrap.Data.ProjectType
9 | ( HaskellOptions (HaskellOptions, haskellOptionsGHCVersion, haskellOptionsHaskellProjectType),
10 | HaskellProjectType
11 | ( HaskellProjectTypeBasic,
12 | HaskellProjectTypeReplOnly,
13 | HaskellProjectTypeServer
14 | ),
15 | )
16 | import Bootstrap.Nix.Expr
17 | ( Expr (ESet),
18 | nix,
19 | nixargs,
20 | nixbinding,
21 | nixproperty,
22 | (|*),
23 | (|.),
24 | (|:),
25 | (|=),
26 | )
27 |
28 | -- | An expression representing the haskell package set bootstrapped. Depends on
29 | -- nixpkgs being in scope.
30 | haskellPackagesExpr :: HaskellOptions -> Expr
31 | haskellPackagesExpr HaskellOptions {..} =
32 | basePackageSet
33 | |. [nixproperty|override|]
34 | |* ESet
35 | False
36 | [ [nixproperty|overrides|]
37 | |= ( [nixargs|_:|]
38 | |: ( [nixargs|super:|]
39 | |: ESet
40 | False
41 | ( ( let overridePrettySimple =
42 | [ [nixbinding|# The override of pretty-simple below may be needed to circumvent a bug in nixpkgs.|],
43 | [nixbinding|# If the devshell builds successfully without it, feel free to remove it.|],
44 | [nixbinding|pretty-simple = super.pretty-simple.overrideAttrs { doCheck = false; };|]
45 | ]
46 | in case haskellOptionsHaskellProjectType of
47 | HaskellProjectTypeReplOnly -> []
48 | HaskellProjectTypeBasic _ -> overridePrettySimple
49 | HaskellProjectTypeServer _ -> overridePrettySimple
50 | )
51 | <> [[nixbinding|# You can overide packages here if you need any dependencies not in this set by default|]]
52 | )
53 | )
54 | )
55 | ]
56 | where
57 | basePackageSet :: Expr
58 | basePackageSet = [nix|nixpkgs.haskell.packages|] |. ghcVersionProperty haskellOptionsGHCVersion
59 |
--------------------------------------------------------------------------------
/.github/workflows/build-prod.yaml:
--------------------------------------------------------------------------------
1 | # © Crown Copyright GCHQ
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | name: Build for production
15 | concurrency:
16 | group: production
17 | cancel-in-progress: true
18 | on:
19 | push:
20 | branches:
21 | - main
22 | jobs:
23 | build-nix-bootstrap:
24 | name: Build nix-bootstrap
25 | runs-on: ubuntu-latest
26 | steps:
27 | - uses: actions/checkout@v3
28 | with:
29 | fetch-depth: 0
30 | - uses: cachix/install-nix-action@v18
31 | with:
32 | nix_path: "nixpkgs=channel:nixpkgs-unstable"
33 | - name: Check version number has been updated
34 | run: >
35 | (git fetch --all && git diff "$(git describe --tags --abbrev=0)" -- package.yaml | grep version) ||
36 | (echo "You must bump the nix-bootstrap version number in package.yaml!" && exit 1)
37 | - name: Get new version number
38 | run: echo "version_number=$(grep -E '^version' package.yaml | cut -d' ' -f2)" >> $GITHUB_ENV
39 | - name: Run pre-commit hooks
40 | run: nix flake check
41 | - name: Build nix-bootstrap
42 | run: nix build
43 | - name: Check for vulnerabilities
44 | run: nix run .\#ciPackages_vulnix -- -C -w vulnerability-whitelist.toml result/
45 | - name: Build release artefact
46 | run: nix run .\#ciPackages_buildBinaryCache
47 | - uses: actions/upload-artifact@v4
48 | with:
49 | name: "release-${{ env.version_number }}"
50 | path: releases/
51 | - uses: rickstaa/action-create-tag@v1
52 | with:
53 | tag: "${{ env.version_number }}"
54 | message: "Release version ${{ env.version_number }}"
55 | - name: Create release
56 | run: >
57 | printf '%s %s -r %s -t %s %s releases/' '-u' "$GITHUB_REPOSITORY_OWNER" "${GITHUB_REPOSITORY#*/}"
58 | "${{ secrets.GITHUB_TOKEN }}" "${{ env.version_number }}" | xargs -d ' ' nix run nixpkgs\#legacyPackages.x86_64-linux.ghr --
59 |
--------------------------------------------------------------------------------
/test/Bootstrap/Data/Bootstrappable/Elm/Review/ConfigSpec.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | module Bootstrap.Data.Bootstrappable.Elm.Review.ConfigSpec (spec) where
5 |
6 | import Bootstrap.Data.Bootstrappable (Bootstrappable (bootstrapContent))
7 | import Bootstrap.Data.Bootstrappable.Elm.Review.Config (elmReviewConfigFor)
8 | import Bootstrap.Data.ProjectType
9 | ( ElmMode (ElmModeBare),
10 | ElmOptions (ElmOptions),
11 | ProjectType (Elm),
12 | )
13 | import Test.Hspec (Spec, describe, it)
14 | import Test.Hspec.Expectations.Pretty (shouldBe)
15 | import Text.RawString.QQ (r)
16 |
17 | spec :: Spec
18 | spec = describe "ReviewConfig.elm rendering" do
19 | it "renders the ReviewConfig.elm correctly" do
20 | bootstrapContent (elmReviewConfigFor . Elm $ ElmOptions ElmModeBare True)
21 | >>= ( `shouldBe`
22 | Right
23 | [r|module ReviewConfig exposing (config)
24 |
25 | import NoBooleanCase
26 | import NoDuplicatePorts
27 | import NoExposingEverything
28 | import NoImportingEverything
29 | import NoMissingSubscriptionsCall
30 | import NoMissingTypeAnnotation
31 | import NoMissingTypeConstructor
32 | import NoMissingTypeExpose
33 | import NoPrematureLetComputation
34 | import NoRecursiveUpdate
35 | import NoSimpleLetBody
36 | import NoUnused.CustomTypeConstructorArgs
37 | import NoUnused.CustomTypeConstructors
38 | import NoUnused.Dependencies
39 | import NoUnused.Exports
40 | import NoUnused.Modules
41 | import NoUnused.Parameters
42 | import NoUnused.Patterns
43 | import NoUnused.Variables
44 | import NoUnusedPorts
45 | import NoUselessSubscriptions
46 | import Review.Rule exposing (Rule)
47 | import Simplify
48 |
49 |
50 | config : List Rule
51 | config =
52 | [ NoBooleanCase.rule
53 | , NoDuplicatePorts.rule
54 | , NoExposingEverything.rule
55 | , NoImportingEverything.rule []
56 | , NoMissingSubscriptionsCall.rule
57 | , NoMissingTypeAnnotation.rule
58 | , NoMissingTypeConstructor.rule
59 | , NoMissingTypeExpose.rule
60 | , NoPrematureLetComputation.rule
61 | , NoRecursiveUpdate.rule
62 | , NoSimpleLetBody.rule
63 | , NoUnusedPorts.rule
64 | , NoUnused.CustomTypeConstructors.rule []
65 | , NoUnused.CustomTypeConstructorArgs.rule
66 | , NoUnused.Dependencies.rule
67 | , NoUnused.Exports.rule
68 | , NoUnused.Modules.rule
69 | , NoUnused.Parameters.rule
70 | , NoUnused.Patterns.rule
71 | , NoUnused.Variables.rule
72 | , NoUselessSubscriptions.rule
73 | , Simplify.rule Simplify.defaults
74 | ]
75 | |]
76 | )
77 |
--------------------------------------------------------------------------------
/test/Bootstrap/Data/Bootstrappable/Elm/Review/ElmJsonSpec.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | module Bootstrap.Data.Bootstrappable.Elm.Review.ElmJsonSpec (spec) where
5 |
6 | import Bootstrap.Data.Bootstrappable (Bootstrappable (bootstrapContent))
7 | import Bootstrap.Data.Bootstrappable.Elm.Review.ElmJson (elmReviewElmJsonFor)
8 | import Bootstrap.Data.ProjectType
9 | ( ElmMode (ElmModeBare),
10 | ElmOptions (ElmOptions),
11 | ProjectType (Elm),
12 | )
13 | import Test.Hspec (Spec, describe, it)
14 | import Test.Hspec.Expectations.Pretty (shouldBe)
15 | import Text.RawString.QQ (r)
16 |
17 | spec :: Spec
18 | spec = describe "elm-review elm.json rendering" do
19 | it "renders the elm-review elm.json correctly" do
20 | bootstrapContent (elmReviewElmJsonFor . Elm $ ElmOptions ElmModeBare True)
21 | >>= ( `shouldBe`
22 | Right
23 | [r|{
24 | "type": "application",
25 | "source-directories": [
26 | "src"
27 | ],
28 | "elm-version": "0.19.1",
29 | "dependencies": {
30 | "direct": {
31 | "Arkham/elm-review-no-missing-type-constructor": "1.0.2",
32 | "elm/core": "1.0.5",
33 | "jfmengels/elm-review": "2.12.2",
34 | "jfmengels/elm-review-code-style": "1.1.3",
35 | "jfmengels/elm-review-common": "1.3.2",
36 | "jfmengels/elm-review-simplify": "2.0.28",
37 | "jfmengels/elm-review-the-elm-architecture": "1.0.3",
38 | "jfmengels/elm-review-unused": "1.1.29",
39 | "sparksp/elm-review-ports": "1.3.1",
40 | "stil4m/elm-syntax": "7.2.9",
41 | "truqu/elm-review-nobooleancase": "1.0.1"
42 | },
43 | "indirect": {
44 | "elm-community/list-extra": "8.7.0",
45 | "elm-explorations/test": "2.1.1",
46 | "elm/bytes": "1.0.8",
47 | "elm/html": "1.0.0",
48 | "elm/json": "1.1.3",
49 | "elm/parser": "1.1.0",
50 | "elm/project-metadata-utils": "1.0.2",
51 | "elm/random": "1.0.0",
52 | "elm/time": "1.0.0",
53 | "elm/virtual-dom": "1.0.3",
54 | "miniBill/elm-unicode": "1.0.3",
55 | "pzp1997/assoc-list": "1.0.0",
56 | "rtfeldman/elm-hex": "1.0.0",
57 | "stil4m/structured-writer": "1.0.3"
58 | }
59 | },
60 | "test-dependencies": {
61 | "direct": {
62 | "elm-explorations/test": "2.1.1"
63 | },
64 | "indirect": {}
65 | }
66 | }
67 | |]
68 | )
69 |
--------------------------------------------------------------------------------
/src/Bootstrap/State.hs:
--------------------------------------------------------------------------------
1 | -- | Copyright : (c) Crown Copyright GCHQ
2 | module Bootstrap.State
3 | ( CursorPos,
4 | InputLine (..),
5 | blankInputLine,
6 | TextInputState (..),
7 | initialTextInputState,
8 | updateCursorPos,
9 | handleCharEntry,
10 | handleBackspacePress,
11 | ChoiceInputState (..),
12 | MultipleChoiceInputState (..),
13 | initialMultipleChoiceInputState,
14 | -- internal; exported for testing
15 | insertIntoInputLine,
16 | backspaceInputLine,
17 | inputLineLength,
18 | )
19 | where
20 |
21 | import qualified Data.Set as Set
22 | import qualified Data.Text as T
23 |
24 | type CursorPos = Int
25 |
26 | newtype InputLine = InputLine {unInputLine :: Text} deriving stock (Eq, Show)
27 |
28 | blankInputLine :: InputLine
29 | blankInputLine = InputLine ""
30 |
31 | insertIntoInputLine :: InputLine -> CursorPos -> Char -> InputLine
32 | insertIntoInputLine (InputLine t) pos c = InputLine $ T.take pos t <> one c <> T.drop pos t
33 |
34 | backspaceInputLine :: InputLine -> CursorPos -> InputLine
35 | backspaceInputLine (InputLine t) pos = InputLine $ T.take (pos - 1) t <> T.drop pos t
36 |
37 | inputLineLength :: InputLine -> CursorPos
38 | inputLineLength = T.length . unInputLine
39 |
40 | data TextInputState = TextInputState
41 | { cursorPos :: CursorPos,
42 | currentLine :: InputLine
43 | }
44 | deriving stock (Eq, Show)
45 |
46 | initialTextInputState :: TextInputState
47 | initialTextInputState = TextInputState 0 blankInputLine
48 |
49 | updateCursorPos :: TextInputState -> (CursorPos -> CursorPos) -> TextInputState
50 | updateCursorPos s f = s {cursorPos = clamp 0 (inputLineLength $ currentLine s) (f $ cursorPos s)}
51 |
52 | handleCharEntry :: TextInputState -> Char -> TextInputState
53 | handleCharEntry s@TextInputState {cursorPos, currentLine} c =
54 | s
55 | { cursorPos = cursorPos + 1,
56 | currentLine = insertIntoInputLine currentLine cursorPos c
57 | }
58 |
59 | handleBackspacePress :: TextInputState -> TextInputState
60 | handleBackspacePress s@TextInputState {cursorPos, currentLine} =
61 | if cursorPos == 0
62 | then s
63 | else
64 | s
65 | { cursorPos = cursorPos - 1,
66 | currentLine = backspaceInputLine currentLine cursorPos
67 | }
68 |
69 | newtype ChoiceInputState a = ChoiceInputState {chosenItem :: a}
70 |
71 | data MultipleChoiceInputState a = MultipleChoiceInputState {chosenItems :: Set a, cursorItem :: a}
72 |
73 | initialMultipleChoiceInputState :: (Bounded a) => MultipleChoiceInputState a
74 | initialMultipleChoiceInputState = MultipleChoiceInputState Set.empty minBound
75 |
--------------------------------------------------------------------------------
/src/Bootstrap/Nix/Expr/ReproducibleBuild/Haskell.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | module Bootstrap.Nix.Expr.ReproducibleBuild.Haskell (reproducibleHaskellBuild) where
5 |
6 | import Bootstrap.Data.ProjectName (ProjectName (unProjectName))
7 | import Bootstrap.Nix.Expr
8 | ( Expr (EIdent, ELetIn, ELit, ESet),
9 | Literal (LMultilineString, LString),
10 | nix,
11 | nixbinding,
12 | nixproperty,
13 | (|*),
14 | (|.),
15 | (|=),
16 | )
17 | import Bootstrap.Nix.Expr.ReproducibleBuild
18 | ( ReproducibleBuildExpr (ReproducibleBuildExpr),
19 | ReproducibleBuildRequirement (RBRHaskellPackages, RBRNixpkgs),
20 | reproducibleBuildRequirementIdentifier,
21 | )
22 | import Text.RawString.QQ (r)
23 |
24 | reproducibleHaskellBuild ::
25 | ProjectName ->
26 | -- | src path
27 | Expr ->
28 | ReproducibleBuildExpr
29 | reproducibleHaskellBuild projectName srcDir =
30 | ReproducibleBuildExpr
31 | ( ELetIn
32 | ( [nixbinding|# This is the core build of your program, but its closure includes all build inputs|]
33 | :| [ [nixproperty|unstripped|]
34 | |= ( (EIdent (reproducibleBuildRequirementIdentifier RBRHaskellPackages) |. [nixproperty|callCabal2nix|])
35 | |* ELit (LString $ unProjectName projectName)
36 | |* srcDir
37 | |* ESet False []
38 | )
39 | ]
40 | )
41 | ( [nix|nixpkgs.stdenv.mkDerivation|]
42 | |* ESet
43 | False
44 | [ [nixbinding|# This derivation strips out the build inputs from `unstripped` above to leave just your program|],
45 | [nixbinding|inherit (unstripped) name src version;|],
46 | [nixbinding|buildInputs = with nixpkgs; [
47 | # This is an assumed set of system libraries needed; you can add to this as necessary.
48 | # You can find out what your package needs by reading the output of `ldd` on your built binary.
49 | glibc
50 | gmp
51 | libffi
52 | # ncurses and zlib are not needed for the bootstrapped program, but are needed by lots of common Haskell libraries
53 | ncurses
54 | zlib
55 | ];|],
56 | [nixproperty|installPhase|]
57 | |= ( ELit . LMultilineString $
58 | [r|
59 | mkdir -p $out/bin
60 | cp ${(nixpkgs.haskell.lib.enableSeparateBinOutput unstripped).bin}/bin/app $out/bin/|]
61 | <> unProjectName projectName
62 | )
63 | ]
64 | )
65 | )
66 | (RBRHaskellPackages :| [RBRNixpkgs])
67 |
--------------------------------------------------------------------------------
/test/Bootstrap/Data/Bootstrappable/HaskellPackagesNixSpec.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | module Bootstrap.Data.Bootstrappable.HaskellPackagesNixSpec (spec) where
5 |
6 | import Bootstrap.Data.Bootstrappable (Bootstrappable (bootstrapContent))
7 | import Bootstrap.Data.Bootstrappable.HaskellPackagesNix (haskellPackagesNixFor)
8 | import Bootstrap.Data.GHCVersion (GHCVersion (GHCVersion))
9 | import Bootstrap.Data.ProjectType
10 | ( HaskellOptions (HaskellOptions),
11 | HaskellProjectType (HaskellProjectTypeBasic, HaskellProjectTypeReplOnly),
12 | ProjectType (Go, Haskell),
13 | SetUpGoBuild (SetUpGoBuild),
14 | SetUpHaskellBuild (SetUpHaskellBuild),
15 | )
16 | import Test.Hspec (Spec, describe, it)
17 | import Test.Hspec.Expectations.Pretty (shouldBe)
18 | import Text.RawString.QQ (r)
19 |
20 | spec :: Spec
21 | spec = describe "haskell-packages.nix rendering" do
22 | it "renders nothing for a non-haskell project" do
23 | haskellPackagesNixFor (Go $ SetUpGoBuild True)
24 | `shouldBe` Nothing
25 | it "renders correctly for a Haskell repl-only project" do
26 | case haskellPackagesNixFor (Haskell $ HaskellOptions (GHCVersion 9 4 2) HaskellProjectTypeReplOnly) of
27 | Just haskellPackagesNix ->
28 | bootstrapContent haskellPackagesNix
29 | >>= ( `shouldBe`
30 | Right
31 | [r|{nixpkgs}:
32 | nixpkgs.haskell.packages.ghc942.override {
33 | overrides = _: super: {
34 | # You can overide packages here if you need any dependencies not in this set by default
35 | };
36 | }
37 | |]
38 | )
39 | Nothing -> fail "Gave nothing for a project which should've had a haskell-packages.nix generated."
40 | it "renders correctly for a full Haskell project" do
41 | case haskellPackagesNixFor (Haskell $ HaskellOptions (GHCVersion 9 4 2) (HaskellProjectTypeBasic $ SetUpHaskellBuild True)) of
42 | Just haskellPackagesNix ->
43 | bootstrapContent haskellPackagesNix
44 | >>= ( `shouldBe`
45 | Right
46 | [r|{nixpkgs}:
47 | nixpkgs.haskell.packages.ghc942.override {
48 | overrides = _: super: {
49 | # The override of pretty-simple below may be needed to circumvent a bug in nixpkgs.
50 | # If the devshell builds successfully without it, feel free to remove it.
51 | pretty-simple = super.pretty-simple.overrideAttrs {
52 | doCheck = false;
53 | };
54 | # You can overide packages here if you need any dependencies not in this set by default
55 | };
56 | }
57 | |]
58 | )
59 | Nothing -> fail "Gave nothing for a project which should've had a haskell-packages.nix generated."
60 |
--------------------------------------------------------------------------------
/src/Bootstrap/Data/Bootstrappable/Elm/Review/Config.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | module Bootstrap.Data.Bootstrappable.Elm.Review.Config
5 | ( ElmReviewConfig,
6 | elmReviewConfigFor,
7 | )
8 | where
9 |
10 | import Bootstrap.Data.Bootstrappable
11 | ( Bootstrappable (bootstrapContent, bootstrapName, bootstrapReason),
12 | )
13 | import Bootstrap.Data.ProjectType
14 | ( ElmOptions (ElmOptions, elmOptionElmMode, elmOptionProvideElmReview),
15 | ProjectType (Elm),
16 | )
17 | import Text.RawString.QQ (r)
18 |
19 | data ElmReviewConfig = ElmReviewConfig
20 |
21 | instance Bootstrappable ElmReviewConfig where
22 | bootstrapName = const "review/src/ReviewConfig.elm"
23 | bootstrapReason = const "The configuration of the elm-review tool"
24 | bootstrapContent =
25 | const . pure $
26 | Right
27 | [r|module ReviewConfig exposing (config)
28 |
29 | import NoBooleanCase
30 | import NoDuplicatePorts
31 | import NoExposingEverything
32 | import NoImportingEverything
33 | import NoMissingSubscriptionsCall
34 | import NoMissingTypeAnnotation
35 | import NoMissingTypeConstructor
36 | import NoMissingTypeExpose
37 | import NoPrematureLetComputation
38 | import NoRecursiveUpdate
39 | import NoSimpleLetBody
40 | import NoUnused.CustomTypeConstructorArgs
41 | import NoUnused.CustomTypeConstructors
42 | import NoUnused.Dependencies
43 | import NoUnused.Exports
44 | import NoUnused.Modules
45 | import NoUnused.Parameters
46 | import NoUnused.Patterns
47 | import NoUnused.Variables
48 | import NoUnusedPorts
49 | import NoUselessSubscriptions
50 | import Review.Rule exposing (Rule)
51 | import Simplify
52 |
53 |
54 | config : List Rule
55 | config =
56 | [ NoBooleanCase.rule
57 | , NoDuplicatePorts.rule
58 | , NoExposingEverything.rule
59 | , NoImportingEverything.rule []
60 | , NoMissingSubscriptionsCall.rule
61 | , NoMissingTypeAnnotation.rule
62 | , NoMissingTypeConstructor.rule
63 | , NoMissingTypeExpose.rule
64 | , NoPrematureLetComputation.rule
65 | , NoRecursiveUpdate.rule
66 | , NoSimpleLetBody.rule
67 | , NoUnusedPorts.rule
68 | , NoUnused.CustomTypeConstructors.rule []
69 | , NoUnused.CustomTypeConstructorArgs.rule
70 | , NoUnused.Dependencies.rule
71 | , NoUnused.Exports.rule
72 | , NoUnused.Modules.rule
73 | , NoUnused.Parameters.rule
74 | , NoUnused.Patterns.rule
75 | , NoUnused.Variables.rule
76 | , NoUselessSubscriptions.rule
77 | , Simplify.rule Simplify.defaults
78 | ]
79 | |]
80 |
81 | elmReviewConfigFor :: ProjectType -> Maybe ElmReviewConfig
82 | elmReviewConfigFor (Elm ElmOptions {..})
83 | | elmOptionProvideElmReview =
84 | Just ElmReviewConfig
85 | elmReviewConfigFor _ = Nothing
86 |
--------------------------------------------------------------------------------
/src/Bootstrap/Data/Bootstrappable/Elm/Review/ElmJson.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | module Bootstrap.Data.Bootstrappable.Elm.Review.ElmJson
5 | ( ElmReviewElmJson,
6 | elmReviewElmJsonFor,
7 | )
8 | where
9 |
10 | import Bootstrap.Data.Bootstrappable
11 | ( Bootstrappable (bootstrapContent, bootstrapName, bootstrapReason),
12 | bootstrapContentPrettyJson,
13 | )
14 | import Bootstrap.Data.ProjectType
15 | ( ElmOptions (ElmOptions, elmOptionElmMode, elmOptionProvideElmReview),
16 | ProjectType (Elm),
17 | )
18 | import Data.Aeson (ToJSON (toJSON))
19 | import Data.Aeson.QQ.Simple (aesonQQ)
20 |
21 | data ElmReviewElmJson = ElmReviewElmJson
22 |
23 | instance Bootstrappable ElmReviewElmJson where
24 | bootstrapName = const "review/elm.json"
25 | bootstrapReason = const "The configuration of the dependencies of the elm-review tool"
26 | bootstrapContent = bootstrapContentPrettyJson ["type", "source-directories", "elm-version"]
27 |
28 | instance ToJSON ElmReviewElmJson where
29 | toJSON ElmReviewElmJson =
30 | [aesonQQ|
31 | {
32 | "type": "application",
33 | "source-directories": ["src"],
34 | "elm-version": "0.19.1",
35 | "dependencies": {
36 | "direct": {
37 | "Arkham/elm-review-no-missing-type-constructor": "1.0.2",
38 | "elm/core": "1.0.5",
39 | "jfmengels/elm-review": "2.12.2",
40 | "jfmengels/elm-review-code-style": "1.1.3",
41 | "jfmengels/elm-review-common": "1.3.2",
42 | "jfmengels/elm-review-simplify": "2.0.28",
43 | "jfmengels/elm-review-the-elm-architecture": "1.0.3",
44 | "jfmengels/elm-review-unused": "1.1.29",
45 | "sparksp/elm-review-ports": "1.3.1",
46 | "stil4m/elm-syntax": "7.2.9",
47 | "truqu/elm-review-nobooleancase": "1.0.1"
48 | },
49 | "indirect": {
50 | "elm/bytes": "1.0.8",
51 | "elm/html": "1.0.0",
52 | "elm/json": "1.1.3",
53 | "elm/parser": "1.1.0",
54 | "elm/project-metadata-utils": "1.0.2",
55 | "elm/random": "1.0.0",
56 | "elm/time": "1.0.0",
57 | "elm/virtual-dom": "1.0.3",
58 | "elm-community/list-extra": "8.7.0",
59 | "elm-explorations/test": "2.1.1",
60 | "miniBill/elm-unicode": "1.0.3",
61 | "pzp1997/assoc-list": "1.0.0",
62 | "rtfeldman/elm-hex": "1.0.0",
63 | "stil4m/structured-writer": "1.0.3"
64 | }
65 | },
66 | "test-dependencies": {
67 | "direct": {
68 | "elm-explorations/test": "2.1.1"
69 | },
70 | "indirect": {}
71 | }
72 | }
73 | |]
74 |
75 | elmReviewElmJsonFor :: ProjectType -> Maybe ElmReviewElmJson
76 | elmReviewElmJsonFor (Elm ElmOptions {..})
77 | | elmOptionProvideElmReview =
78 | Just ElmReviewElmJson
79 | elmReviewElmJsonFor _ = Nothing
80 |
--------------------------------------------------------------------------------
/src/Bootstrap/Nix/Flake.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 | {-# LANGUAGE TemplateHaskellQuotes #-}
3 | {-# LANGUAGE TypeApplications #-}
4 |
5 | -- | Copyright : (c) Crown Copyright GCHQ
6 | module Bootstrap.Nix.Flake (generateIntermediateFlake, intermediateFlake) where
7 |
8 | import Bootstrap.Cli (RunConfig (RunConfig, rcNonInteractive))
9 | import Bootstrap.Data.ProjectName (ProjectName (unProjectName))
10 | import Bootstrap.Data.Target (Target)
11 | import Bootstrap.Error
12 | ( CanDieOnError (dieOnError'),
13 | InProgressDuration (LongRunning),
14 | runWithProgressMsg,
15 | )
16 | import Bootstrap.GitPod (resetPermissionsInGitPod)
17 | import Bootstrap.Monad (MonadBootstrap)
18 | import Bootstrap.Nix.Evaluate (NixBinaryPaths, runNix)
19 | import Bootstrap.Nix.Expr
20 | ( CommentsPolicy (ShowComments),
21 | Expr (ELit, ESet),
22 | Literal (LString),
23 | nixbinding,
24 | nixproperty,
25 | writeExpr,
26 | (|=),
27 | )
28 | import Bootstrap.Nix.Expr.FlakeInputs (nixpkgsSrcInputBinding)
29 | import Bootstrap.Terminal (promptYesNoWithDefault)
30 | import Bootstrap.Unix (git)
31 | import Control.Exception (IOException)
32 | import Control.Monad.Catch (MonadCatch, try)
33 | import System.Terminal (MonadPrinter (putTextLn))
34 |
35 | generateIntermediateFlake :: (MonadBootstrap m) => NixBinaryPaths -> RunConfig -> ProjectName -> Target -> m ()
36 | generateIntermediateFlake nixBinaryPaths RunConfig {rcNonInteractive} projectName target =
37 | promptYesNoWithDefault
38 | (if rcNonInteractive then Just True else Nothing)
39 | "First, I need to pin a version of nixpkgs in flake.nix. Is that okay?"
40 | >>= \case
41 | True -> do
42 | resetPermissionsInGitPod
43 | void
44 | . dieOnError'
45 | . ExceptT
46 | . runWithProgressMsg LongRunning "Pinning a version of nixpkgs"
47 | $ do
48 | writeIntermediateFlake projectName target
49 | void . ExceptT $ git ["add", "--intent-to-add", "flake.nix"]
50 | void . ExceptT $ runNix nixBinaryPaths ["flake", "lock"]
51 | False -> putTextLn "Okay, exiting." *> exitFailure
52 |
53 | writeIntermediateFlake :: (MonadCatch m, MonadIO m) => ProjectName -> Target -> ExceptT IOException m ()
54 | writeIntermediateFlake projectName target =
55 | void
56 | . try @_ @IOException
57 | . writeFileText "flake.nix"
58 | . (<> "\n")
59 | . writeExpr ShowComments
60 | $ intermediateFlake projectName target
61 |
62 | intermediateFlake :: ProjectName -> Target -> Expr
63 | intermediateFlake projectName target =
64 | ESet
65 | False
66 | [ [nixproperty|description|] |= ELit (LString ("Development infrastructure for " <> unProjectName projectName)),
67 | [nixproperty|inputs|] |= ESet False [nixpkgsSrcInputBinding target],
68 | [nixbinding|outputs = _: {};|]
69 | ]
70 |
--------------------------------------------------------------------------------
/src/Bootstrap/Data/GHCVersion.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE DataKinds #-}
2 | {-# LANGUAGE DeriveGeneric #-}
3 | {-# LANGUAGE DerivingVia #-}
4 | {-# LANGUAGE TypeOperators #-}
5 |
6 | -- | Copyright : (c) Crown Copyright GCHQ
7 | -- Description : Represent versions of the Glasgow Haskell Compiler
8 | module Bootstrap.Data.GHCVersion
9 | ( -- * Data
10 | GHCVersion (..),
11 |
12 | -- * Parsing
13 | parseGHCVersion,
14 |
15 | -- * Formatting
16 | printGHCVersion,
17 |
18 | -- * Use in Nix expressions
19 | ghcVersionProperty,
20 | )
21 | where
22 |
23 | import Bootstrap.Nix.Expr (Identifier (Identifier), Property (PIdent))
24 | import qualified Data.Char as C
25 | import qualified Data.Text as T
26 | import Dhall (FromDhall, ToDhall)
27 | import Dhall.Deriving
28 | ( CamelCase,
29 | Codec (Codec),
30 | DropPrefix,
31 | Field,
32 | type (<<<),
33 | )
34 | import qualified Relude.Unsafe as Unsafe
35 | import Text.Megaparsec
36 | ( MonadParsec (label, takeWhile1P),
37 | Parsec,
38 | failure,
39 | )
40 | import Text.Megaparsec.Char (string)
41 | import qualified Text.Megaparsec.Error as ParseError
42 |
43 | -- | Represents a version of the Glasgow Haskell Compiler
44 | data GHCVersion = GHCVersion
45 | { ghcVersionMajor :: Natural,
46 | ghcVersionMinor :: Natural,
47 | ghcVersionPatch :: Natural
48 | }
49 | deriving stock (Eq, Generic, Ord, Show)
50 | deriving (FromDhall, ToDhall) via Codec (Field (CamelCase <<< DropPrefix "ghcVersion")) GHCVersion
51 |
52 | -- | Gets the attribute name of the GHC version, able to be queried in nixpkgs
53 | ghcVersionProperty :: GHCVersion -> Property
54 | ghcVersionProperty = PIdent . Identifier . ("ghc" <>) . T.filter (/= '.') . printGHCVersion
55 |
56 | -- | Parses a nixpkgs attribute representing a GHC version
57 | --
58 | -- Use `ghcVersionAttributeName` to produce this form
59 | parseGHCVersion :: Parsec Void String GHCVersion
60 | parseGHCVersion = label "GHC Version attribute" do
61 | void $ string "ghc"
62 | -- Safe because we have the `isDigit` predicate
63 | digits <- Unsafe.read . one <<$>> takeWhile1P (Just "version digits") C.isDigit
64 | case digits of
65 | [x, y, z] -> pure $ GHCVersion x y z
66 | [x, y1, y2, z] -> pure $ GHCVersion x (twoDigitVersionNumber y1 y2) z
67 | [x1, x2, y1, y2, z] -> pure $ GHCVersion (twoDigitVersionNumber x1 x2) (twoDigitVersionNumber y1 y2) z
68 | _ -> failure Nothing $ fromList [ParseError.Label ('v' :| "ersion digits")]
69 | where
70 | twoDigitVersionNumber :: Natural -> Natural -> Natural
71 | twoDigitVersionNumber x1 x2 = Unsafe.read $ show x1 <> show x2
72 |
73 | -- | Print out the version in a human-readable format
74 | --
75 | -- Not an inverse of `parseGHCVersion` - see `ghcVersionAttributeName`
76 | printGHCVersion :: GHCVersion -> Text
77 | printGHCVersion (GHCVersion major minor patch) =
78 | T.intercalate "." $ show <$> [major, minor, patch]
79 |
--------------------------------------------------------------------------------
/src/Bootstrap/Nix/Expr/MkShell.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 | {-# LANGUAGE TemplateHaskellQuotes #-}
3 |
4 | -- | Copyright : (c) Crown Copyright GCHQ
5 | module Bootstrap.Nix.Expr.MkShell (BuildInputSpec (..), mkShell) where
6 |
7 | import Bootstrap.Data.PreCommitHook
8 | ( PreCommitHooksConfig (PreCommitHooksConfig),
9 | )
10 | import Bootstrap.Data.ProjectType
11 | ( HasProjectSuperType (projectSuperType),
12 | ProjectSuperType (PSTJava, PSTRust),
13 | ProjectType,
14 | jdkPackageName,
15 | )
16 | import Bootstrap.Nix.Expr
17 | ( Binding,
18 | Expr (ELit, ESet),
19 | Literal (LMultilineString),
20 | nix,
21 | nixbinding,
22 | nixproperty,
23 | (|*),
24 | (|=),
25 | )
26 | import Bootstrap.Nix.Expr.BuildInputs
27 | ( BuildInputSpec (BuildInputSpec, bisNixpkgsPackages, bisPreCommitHooksConfig, bisProjectType),
28 | buildInputsBindings,
29 | )
30 |
31 | -- | A nixpkgs.mkShell expression. Expects `nixpkgs` to be in scope.
32 | mkShell :: BuildInputSpec ProjectType -> Expr
33 | mkShell buildInputSpec@BuildInputSpec {bisPreCommitHooksConfig, bisProjectType} =
34 | [nix|nixpkgs.mkShell|]
35 | |* ESet
36 | False
37 | ( buildInputsBindings buildInputSpec
38 | <> toList (shellHookBinding <$> shellHookFor bisPreCommitHooksConfig bisProjectType)
39 | )
40 |
41 | data ShellHook
42 | = ShellHookFromPreCommit
43 | | ShellHookJava Text
44 | | ShellHookRust
45 | | ShellHookCombined (NonEmpty ShellHook)
46 |
47 | shellHookFor :: PreCommitHooksConfig -> ProjectType -> Maybe ShellHook
48 | shellHookFor pchc pt = case (pchc, projectSuperType pt) of
49 | (PreCommitHooksConfig True, PSTJava) -> Just $ ShellHookCombined (ShellHookJava (jdkPackageName pt) :| [ShellHookFromPreCommit])
50 | (PreCommitHooksConfig False, PSTJava) -> Just $ ShellHookJava (jdkPackageName pt)
51 | (PreCommitHooksConfig True, PSTRust) -> Just $ ShellHookCombined (ShellHookRust :| [ShellHookFromPreCommit])
52 | (PreCommitHooksConfig False, PSTRust) -> Just ShellHookRust
53 | (PreCommitHooksConfig True, _) -> Just ShellHookFromPreCommit
54 | _ -> Nothing
55 |
56 | shellHookBinding :: ShellHook -> Binding
57 | shellHookBinding = \case
58 | ShellHookFromPreCommit -> [nixbinding|inherit (preCommitHooks.allHooks) shellHook;|]
59 | x -> withComponents $ shellHookComponentBinding x
60 | where
61 | withComponents :: [Text] -> Binding
62 | withComponents xs =
63 | [nixproperty|shellHook|]
64 | |= ELit
65 | (LMultilineString $ mconcat (("\n " <>) <$> xs) <> "\n ")
66 | shellHookComponentBinding :: ShellHook -> [Text]
67 | shellHookComponentBinding = \case
68 | ShellHookFromPreCommit -> ["${preCommitHooks.allHooks.shellHook}"]
69 | ShellHookJava jdk -> ["export JAVA_HOME=\"${nixpkgs."] ++ [jdk] ++ ["}\""]
70 | ShellHookRust -> ["export RUST_SRC_PATH=${nixpkgs.rustPlatform.rustLibSrc}"]
71 | ShellHookCombined xs -> sconcat $ shellHookComponentBinding <$> xs
72 |
--------------------------------------------------------------------------------
/test/Bootstrap/Data/Config/InternalSpec.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE FlexibleInstances #-}
2 | {-# LANGUAGE QuasiQuotes #-}
3 | {-# OPTIONS_GHC -Wno-orphans #-}
4 |
5 | -- | Copyright : (c) Crown Copyright GCHQ
6 | module Bootstrap.Data.Config.InternalSpec (spec) where
7 |
8 | import Bootstrap.Data.Bootstrappable (Bootstrappable (bootstrapContent))
9 | import Bootstrap.Data.Config (configFor)
10 | import Bootstrap.Data.ContinuousIntegration (ContinuousIntegrationConfig (ContinuousIntegrationConfig))
11 | import Bootstrap.Data.DevContainer (DevContainerConfig (DevContainerConfig))
12 | import Bootstrap.Data.PreCommitHook (PreCommitHooksConfig (PreCommitHooksConfig))
13 | import Bootstrap.Data.ProjectName (mkProjectName)
14 | import Bootstrap.Data.ProjectType (NodePackageManager (PNPm), ProjectType (Node))
15 | import Bootstrap.Data.ProjectTypeSpec ()
16 | import Bootstrap.Data.Target (Target (TargetDefault))
17 | import qualified Relude.Unsafe as Unsafe
18 | import Test.Hspec (Spec, describe, it)
19 | import Test.Hspec.Expectations.Pretty (shouldBe)
20 | import Text.RawString.QQ (r)
21 |
22 | spec :: Spec
23 | spec = describe ".nix-bootstrap.dhall rendering" do
24 | it "renders correctly" do
25 | let projectName = Unsafe.fromJust $ mkProjectName "test-project"
26 | bootstrapContent
27 | ( configFor
28 | projectName
29 | (Node PNPm)
30 | (PreCommitHooksConfig True)
31 | (ContinuousIntegrationConfig True)
32 | (DevContainerConfig True)
33 | TargetDefault
34 | )
35 | >>= ( `shouldBe`
36 | Right
37 | [r|-- This file was generated by nix-bootstrap.
38 | -- It should be checked into version control.
39 | -- It is used to aid migration between nix-bootstrap versions and preserve idempotence.
40 | let NodePackageManager = < NPM | PNPm | Yarn >
41 |
42 | let ElmMode = < Bare | Node : NodePackageManager >
43 |
44 | let ElmOptions = { elmMode : ElmMode, provideElmReview : Bool }
45 |
46 | let HaskellProjectType = < ReplOnly | Basic : Bool | Server : Bool >
47 |
48 | let HaskellOptions =
49 | { ghcVersion : { major : Natural, minor : Natural, patch : Natural }
50 | , haskellProjectType : HaskellProjectType
51 | }
52 |
53 | let JavaOptions =
54 | { installMinishift : Bool
55 | , installLombok : Bool
56 | , setUpJavaBuild : < SetUpJavaBuild : Text | NoJavaBuild >
57 | , jdk : < OpenJDK | GraalVM >
58 | }
59 |
60 | let ProjectType =
61 | < Minimal
62 | | Elm : ElmOptions
63 | | Haskell : HaskellOptions
64 | | Node : NodePackageManager
65 | | Go : Bool
66 | | Java : JavaOptions
67 | | Python
68 | | Rust
69 | >
70 |
71 | in { projectName = "test-project"
72 | , projectType = ProjectType.Node NodePackageManager.PNPm
73 | , setUpPreCommitHooks = True
74 | , setUpContinuousIntegration = True
75 | , setUpVSCodeDevContainer = True
76 | , target = {=}
77 | }
78 | |]
79 | )
80 |
--------------------------------------------------------------------------------
/src/Bootstrap/Data/Bootstrappable/Haskell/MainHs.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 | {-# LANGUAGE TemplateHaskellQuotes #-}
3 |
4 | -- | Copyright : (c) Crown Copyright GCHQ
5 | module Bootstrap.Data.Bootstrappable.Haskell.MainHs
6 | ( MainHs,
7 | mainHsFor,
8 | )
9 | where
10 |
11 | import Bootstrap.Data.Bootstrappable
12 | ( Bootstrappable (bootstrapContent, bootstrapName, bootstrapReason),
13 | HaskellImport (HaskellImport),
14 | bootstrapContentHaskell,
15 | haskellModule,
16 | haskellModuleDecs,
17 | haskellModuleImports,
18 | )
19 | import Bootstrap.Data.ProjectType
20 | ( HaskellOptions (HaskellOptions),
21 | HaskellProjectType (HaskellProjectTypeBasic, HaskellProjectTypeReplOnly, HaskellProjectTypeServer),
22 | ProjectType (Haskell),
23 | )
24 | import Control.Lens ((?~))
25 | import Language.Haskell.TH (appE, conT, varE)
26 | import Language.Haskell.TH.Syntax
27 | ( Body (NormalB),
28 | Clause (Clause),
29 | Dec (FunD, SigD),
30 | Exp (DoE, VarE),
31 | ModName (ModName),
32 | Stmt (NoBindS),
33 | mkName,
34 | )
35 |
36 | data MainHs
37 | = -- | Simply calls Lib.lib
38 | MainHsLib
39 | | -- | Uses Server.app (a WAI Application) to run a warp server on port 8080
40 | MainHsWarp
41 |
42 | instance Bootstrappable MainHs where
43 | bootstrapName = const "app/Main.hs"
44 | bootstrapReason = const "The entrypoint of your haskell executable"
45 | bootstrapContent mainHs = do
46 | let app = mkName "app"
47 | lib = mkName "lib"
48 | main = mkName "main"
49 | run = mkName "run"
50 | ioUnit <- [t|$(conT $ mkName "IO") ()|]
51 | logServing <- [|putTextLn "Serving on 8080..."|]
52 | runServer <- varE run `appE` [|8080|] `appE` varE app
53 | pure . pure . bootstrapContentHaskell $
54 | haskellModule (ModName "Main") (one "main")
55 | & haskellModuleImports
56 | ?~ ( case mainHs of
57 | MainHsLib -> one (HaskellImport (ModName "Lib") ["lib"])
58 | MainHsWarp ->
59 | HaskellImport (ModName "Network.Wai.Handler.Warp") ["run"]
60 | :| [HaskellImport (ModName "Server") ["app"]]
61 | )
62 | & haskellModuleDecs
63 | ?~ SigD main ioUnit
64 | :| [ FunD
65 | main
66 | [ Clause
67 | []
68 | ( NormalB $ case mainHs of
69 | MainHsLib -> VarE lib
70 | MainHsWarp -> DoE Nothing [NoBindS logServing, NoBindS runServer]
71 | )
72 | []
73 | ]
74 | ]
75 |
76 | mainHsFor :: ProjectType -> Maybe MainHs
77 | mainHsFor = \case
78 | Haskell (HaskellOptions _ haskellProjectType) -> case haskellProjectType of
79 | HaskellProjectTypeReplOnly -> Nothing
80 | HaskellProjectTypeBasic _ -> Just MainHsLib
81 | HaskellProjectTypeServer _ -> Just MainHsWarp
82 | _ -> Nothing
83 |
--------------------------------------------------------------------------------
/flake.lock:
--------------------------------------------------------------------------------
1 | {
2 | "nodes": {
3 | "flake-compat": {
4 | "flake": false,
5 | "locked": {
6 | "lastModified": 1761588595,
7 | "narHash": "sha256-XKUZz9zewJNUj46b4AJdiRZJAvSZ0Dqj2BNfXvFlJC4=",
8 | "owner": "edolstra",
9 | "repo": "flake-compat",
10 | "rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5",
11 | "type": "github"
12 | },
13 | "original": {
14 | "owner": "edolstra",
15 | "repo": "flake-compat",
16 | "type": "github"
17 | }
18 | },
19 | "gitignore": {
20 | "inputs": {
21 | "nixpkgs": [
22 | "pre-commit-hooks-lib",
23 | "nixpkgs"
24 | ]
25 | },
26 | "locked": {
27 | "lastModified": 1709087332,
28 | "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
29 | "owner": "hercules-ci",
30 | "repo": "gitignore.nix",
31 | "rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
32 | "type": "github"
33 | },
34 | "original": {
35 | "owner": "hercules-ci",
36 | "repo": "gitignore.nix",
37 | "type": "github"
38 | }
39 | },
40 | "nixpkgs-src": {
41 | "locked": {
42 | "lastModified": 1764521362,
43 | "narHash": "sha256-M101xMtWdF1eSD0xhiR8nG8CXRlHmv6V+VoY65Smwf4=",
44 | "owner": "NixOS",
45 | "repo": "nixpkgs",
46 | "rev": "871b9fd269ff6246794583ce4ee1031e1da71895",
47 | "type": "github"
48 | },
49 | "original": {
50 | "id": "nixpkgs",
51 | "ref": "25.11",
52 | "type": "indirect"
53 | }
54 | },
55 | "nixpkgs-src-vulnix": {
56 | "locked": {
57 | "lastModified": 1756150737,
58 | "narHash": "sha256-7FdMmyjNM8mQSf3yvyKitw40sdKDL60KLU+l6sAYw7E=",
59 | "owner": "NixOS",
60 | "repo": "nixpkgs",
61 | "rev": "33d83ff29f05f9b2e30cd05b2f60b02a1fbe8a46",
62 | "type": "github"
63 | },
64 | "original": {
65 | "id": "nixpkgs",
66 | "rev": "33d83ff29f05f9b2e30cd05b2f60b02a1fbe8a46",
67 | "type": "indirect"
68 | }
69 | },
70 | "pre-commit-hooks-lib": {
71 | "inputs": {
72 | "flake-compat": "flake-compat",
73 | "gitignore": "gitignore",
74 | "nixpkgs": [
75 | "nixpkgs-src"
76 | ]
77 | },
78 | "locked": {
79 | "lastModified": 1763988335,
80 | "narHash": "sha256-QlcnByMc8KBjpU37rbq5iP7Cp97HvjRP0ucfdh+M4Qc=",
81 | "owner": "cachix",
82 | "repo": "pre-commit-hooks.nix",
83 | "rev": "50b9238891e388c9fdc6a5c49e49c42533a1b5ce",
84 | "type": "github"
85 | },
86 | "original": {
87 | "owner": "cachix",
88 | "repo": "pre-commit-hooks.nix",
89 | "type": "github"
90 | }
91 | },
92 | "root": {
93 | "inputs": {
94 | "nixpkgs-src": "nixpkgs-src",
95 | "nixpkgs-src-vulnix": "nixpkgs-src-vulnix",
96 | "pre-commit-hooks-lib": "pre-commit-hooks-lib"
97 | }
98 | }
99 | },
100 | "root": "root",
101 | "version": 7
102 | }
103 |
--------------------------------------------------------------------------------
/package.yaml:
--------------------------------------------------------------------------------
1 | # © Crown Copyright GCHQ
2 |
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 |
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | name: nix-bootstrap
15 | version: 2.5.1.1
16 | author: gchquser
17 | maintainer: 48051938+sd234678@users.noreply.github.com
18 | copyright: Crown Copyright
19 | license: Apache-2.0
20 | default-extensions:
21 | - BlockArguments
22 | - DerivingStrategies
23 | - GADTs
24 | - LambdaCase
25 | - NamedFieldPuns
26 | - OverloadedStrings
27 | - RecordWildCards
28 | - StrictData
29 | dependencies:
30 | - name: base
31 | version: "== 4.20.1.0"
32 | mixin:
33 | - hiding (Prelude)
34 | - relude == 1.2.2.2
35 | flags:
36 | prod:
37 | default: false
38 | description: Enable production defaults
39 | manual: true
40 | ghc-options:
41 | - "-Wall"
42 | - "-Wcpp-undef"
43 | - "-Widentities"
44 | - "-Wincomplete-patterns"
45 | - "-Wincomplete-record-updates"
46 | - "-Wincomplete-uni-patterns"
47 | - "-Wmissing-deriving-strategies"
48 | - "-Wmissing-export-lists"
49 | - "-Wmissing-import-lists"
50 | - "-Wmissing-signatures"
51 | - "-Wpartial-fields"
52 | - "-Wredundant-constraints"
53 | when:
54 | - condition: flag(prod)
55 | ghc-options:
56 | - "-O2"
57 | - "-Werror"
58 | library:
59 | source-dirs: src
60 | dependencies:
61 | - aeson == 2.2.3.0
62 | - aeson-pretty == 0.8.10
63 | - blaze-html == 0.9.2.0
64 | - directory == 1.3.8.5
65 | - dhall == 1.42.3
66 | - exceptions == 0.10.9
67 | - extra == 1.8
68 | - filepath == 1.5.4.0
69 | - lens == 5.3.5
70 | - megaparsec == 9.7.0
71 | - mtl == 2.3.1
72 | - parser-combinators == 1.3.0
73 | - process == 1.6.25.0
74 | - raw-strings-qq == 1.1
75 | - regex-compat == 0.95.2.2
76 | - silently == 1.2.5.4
77 | - singletons == 3.0.4
78 | - template-haskell == 2.22.0.0
79 | - terminal == 0.2.0.0
80 | - th-abstraction == 0.7.1.0
81 | - tomland == 1.3.3.3
82 | - which == 0.2.0.3
83 | - yaml == 0.11.11.2
84 | executables:
85 | app:
86 | main: Main.hs
87 | source-dirs: app
88 | ghc-options:
89 | - "-O2"
90 | - "-threaded"
91 | - "-rtsopts"
92 | - "-with-rtsopts=-N"
93 | dependencies:
94 | - nix-bootstrap
95 | tests:
96 | nix-bootstrap-test:
97 | main: Spec.hs
98 | source-dirs: test
99 | dependencies:
100 | - dhall == 1.42.3
101 | - hspec == 2.11.14
102 | - hspec-expectations-pretty-diff == 0.7.2.6
103 | - nix-bootstrap
104 | - QuickCheck == 2.15.0.1
105 | - raw-strings-qq == 1.1
106 | - tomland == 1.3.3.3
107 | build-tools:
108 | - hspec-discover == 2.11.14
109 |
--------------------------------------------------------------------------------
/src/Bootstrap/Data/Config/Internal/TH.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE TemplateHaskellQuotes #-}
2 | {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}
3 |
4 | -- |
5 | -- Description : Functions used for generating code relating to Config
6 | -- Copyright : (c) Crown Copyright GCHQ
7 | module Bootstrap.Data.Config.Internal.TH (makeConfigLenses) where
8 |
9 | import Bootstrap.Data.Config.Internal (Config, _VersionedProjectType)
10 | import Bootstrap.Data.ProjectType (ProjectType)
11 | import Control.Lens (Lens')
12 | import qualified Data.Char as C
13 | import Data.Foldable (foldrM)
14 | import qualified Data.Text as T
15 | import Language.Haskell.TH
16 | ( Body (NormalB),
17 | Dec (SigD, ValD),
18 | DecsQ,
19 | Exp (InfixE, VarE),
20 | Name,
21 | Pat (VarP),
22 | Quote (newName),
23 | Type (AppT, ConT),
24 | lookupValueName,
25 | nameBase,
26 | )
27 | import Language.Haskell.TH.Datatype as D
28 | ( ConstructorInfo (constructorFields, constructorVariant),
29 | ConstructorVariant (RecordConstructor),
30 | reifyConstructor,
31 | )
32 | import qualified Relude.Unsafe as Unsafe
33 |
34 | -- | Makes lenses for the given data constructor without
35 | -- the config version number in them
36 | makeConfigLenses :: Name -> DecsQ
37 | makeConfigLenses name = do
38 | constructorInfo <- D.reifyConstructor name
39 | let D.RecordConstructor fieldNames = D.constructorVariant constructorInfo
40 | zipped = zip fieldNames (D.constructorFields constructorInfo)
41 | foldrM makeConfigLens [] zipped
42 | where
43 | makeConfigLens :: (Name, Language.Haskell.TH.Type) -> [Dec] -> DecsQ
44 | makeConfigLens (constructorName, constructorType) acc = do
45 | let baseFieldName = toText $ nameBase constructorName
46 | digits = T.takeWhile C.isDigit $ T.dropWhile (not . C.isDigit) baseFieldName
47 | fieldPrefix <-
48 | if T.null digits
49 | then fail "Field constructor should have config version digit in it"
50 | else pure $ "_configV" <> digits
51 | lensNameBody <-
52 | maybe
53 | (fail $ "Expected constructor to have prefix " <> toString fieldPrefix)
54 | pure
55 | (T.stripPrefix fieldPrefix baseFieldName)
56 | let lensNameStr = toString . ("config" <>) . (\(c, s) -> T.toUpper (one c) <> s) . Unsafe.fromJust $ T.uncons lensNameBody
57 | lensName <- newName lensNameStr
58 | versionedLensName <- Unsafe.fromJust <$> lookupValueName (drop 1 (toString fieldPrefix) <> toString lensNameBody)
59 | projectTypeType <- [t|ProjectType|]
60 | _VersionedProjectType' <- Just <$> [|_VersionedProjectType|]
61 | InfixE wrapped compose _ <- [e|_Current . x|]
62 | let isProjectTypeField = nameBase lensName == "configProjectType"
63 | versionedLens = Just $ VarE versionedLensName
64 | constructorType' = if isProjectTypeField then projectTypeType else constructorType
65 | body =
66 | NormalB . InfixE wrapped compose $
67 | if isProjectTypeField
68 | then Just (InfixE versionedLens compose _VersionedProjectType')
69 | else versionedLens
70 | sig = SigD lensName (AppT (AppT (ConT ''Lens') (ConT ''Config)) constructorType')
71 | impl = ValD (VarP lensName) body []
72 | pure $ acc <> [sig, impl]
73 |
--------------------------------------------------------------------------------
/.devcontainer/Dockerfile:
--------------------------------------------------------------------------------
1 | # © Crown Copyright GCHQ
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | FROM ubuntu:22.04 as base
15 |
16 | # Set shell and check for pipe fails
17 | SHELL ["/bin/bash", "-o", "pipefail", "-c"]
18 |
19 | # Install deps required by Nix installer
20 | RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends \
21 | ca-certificates \
22 | curl \
23 | sudo \
24 | xz-utils
25 |
26 | # Create user
27 | RUN groupadd -g 1001 vscode && \
28 | useradd -u 1001 -g 1001 -G sudo -m vscode -s /bin/bash
29 |
30 | # Configure sudo and Nix
31 | RUN sed -i 's/%sudo.*ALL/%sudo ALL=(ALL:ALL) NOPASSWD:ALL/' /etc/sudoers && \
32 | echo "sandbox = false" > /etc/nix.conf && \
33 | echo "experimental-features = nix-command flakes" >> /etc/nix.conf
34 |
35 | # Install Nix and enable flakes
36 | USER vscode
37 | ENV USER=vscode
38 | ENV NIX_PATH=/home/vscode/.nix-defexpr/channels:/nix/var/nix/profiles/per-user/root/channels
39 | ENV NIX_CONF_DIR /etc
40 | RUN curl -L https://nixos.org/nix/install | NIX_INSTALLER_NO_MODIFY_PROFILE=1 sh
41 |
42 | FROM ubuntu:22.04
43 |
44 | ENV NIX_CONF_DIR /etc
45 |
46 | ## Install vscode deps
47 | RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends \
48 | ca-certificates \
49 | git \
50 | gnupg \
51 | gnupg2 \
52 | locales \
53 | ssh \
54 | sudo && \
55 | rm -rf /var/lib/apt/lists/*
56 |
57 | # Create user
58 | RUN groupadd -g 1001 vscode && \
59 | useradd -u 1001 -g 1001 -G sudo -m vscode -s /bin/bash
60 | COPY --from=base --chown=vscode:vscode /home/vscode /home/vscode
61 |
62 | # Configure en_US.UTF-8 locale
63 | RUN echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen && \
64 | locale-gen
65 |
66 | # Configure sudo
67 | RUN sed -i 's/%sudo.*ALL/%sudo ALL=(ALL:ALL) NOPASSWD:ALL/' /etc/sudoers
68 |
69 | # Setup Nix environment
70 | RUN echo "source /home/vscode/.nix-profile/etc/profile.d/nix.sh" >> /etc/bash.bashrc && \
71 | echo "source /home/vscode/.nix-profile/etc/profile.d/nix.sh" >> /etc/zshrc
72 |
73 | # Copy nix and configs
74 | COPY --from=base /nix /nix
75 | COPY --from=base /etc/nix.conf /etc/nix.conf
76 |
77 | USER vscode
78 |
79 | # Setup vscode
80 | RUN mkdir -p /home/vscode/.vscode-server/extensions && \
81 | mkdir -p /home/vscode/.vscode-server-insiders/extensions
82 |
83 | RUN export PATH=$PATH:/home/vscode/.nix-profile/bin \
84 | && nix-channel --add https://nixos.org/channels/nixpkgs-unstable nixpkgs && nix-channel --update \
85 | && nix-env -iA nixpkgs.bashInteractive nixpkgs.direnv nixpkgs.git \
86 | && echo 'eval "$(direnv hook bash)"' >> ~/.bashrc
87 |
88 | RUN mkdir -p ~/.config/direnv && touch ~/.config/direnv/direnvrc
89 |
90 | ENV SHELL="/home/vscode/.nix-profile/bin/bash"
91 | ENV PATH="${PATH}:/home/vscode/.nix-profile/bin"
92 |
--------------------------------------------------------------------------------
/flake.nix:
--------------------------------------------------------------------------------
1 | # © Crown Copyright GCHQ
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | {
15 | description = "Development infrastructure for nix-bootstrap";
16 | inputs = {
17 | nixpkgs-src.url = "nixpkgs/25.11";
18 | nixpkgs-src-vulnix.url = "nixpkgs/33d83ff29f05f9b2e30cd05b2f60b02a1fbe8a46";
19 | pre-commit-hooks-lib = {
20 | inputs.nixpkgs.follows = "nixpkgs-src";
21 | url = "github:cachix/pre-commit-hooks.nix";
22 | };
23 | };
24 |
25 | outputs = {
26 | self,
27 | nixpkgs-src,
28 | nixpkgs-src-vulnix,
29 | pre-commit-hooks-lib,
30 | ...
31 | }: let
32 | systemsHelpers = import nix/systems.nix;
33 | allSystems = nixpkgs-src.lib.platforms.all;
34 | supportedSystems = with systemsHelpers.system allSystems; [x86_64-linux aarch64-linux];
35 | in
36 | systemsHelpers.forEachSystem supportedSystems (
37 | system: let
38 | nixpkgs = nixpkgs-src.legacyPackages.${system};
39 | nixpkgs-vulnix = nixpkgs-src-vulnix.legacyPackages.${system};
40 | inherit
41 | (import nix/haskell-env.nix {inherit nixpkgs;})
42 | baseHaskellPackages
43 | haskellDevTools
44 | ;
45 | inherit (import nix/release.nix {inherit baseHaskellPackages nixpkgs;}) nix-bootstrap;
46 | buildBinaryCache = nixpkgs.writeShellScriptBin "buildBinaryCache" ''
47 | sed=${nixpkgs.gnused}/bin/sed
48 | tar=${nixpkgs.gnutar}/bin/tar
49 | ${builtins.readFile scripts/build-binary-cache.sh}
50 | '';
51 | pre-commit-hooks = import nix/pre-commit-hooks.nix {
52 | inherit nixpkgs pre-commit-hooks-lib system;
53 | inherit (nixpkgs) alejandra;
54 | inherit (nixpkgs-vulnix) vulnix;
55 | src = ./.;
56 | };
57 | extraDevShellArgs = {
58 | inherit (pre-commit-hooks.allHooks) shellHook;
59 | };
60 | in {
61 | checks = {pre-commit-check = pre-commit-hooks.pureHooks;};
62 | devShells = {
63 | default = nixpkgs.mkShell ({
64 | buildInputs =
65 | [buildBinaryCache]
66 | ++ haskellDevTools
67 | ++ pre-commit-hooks.tools
68 | ++ (
69 | with nixpkgs; [
70 | coreutils
71 | dhall
72 | glab
73 | gnused
74 | gnutar
75 | which
76 | ]
77 | );
78 | }
79 | // extraDevShellArgs);
80 | };
81 | packages = {
82 | default = nix-bootstrap;
83 | inherit nix-bootstrap;
84 | # To be used as tools in CI
85 | ciPackages_buildBinaryCache = buildBinaryCache;
86 | ciPackages_vulnix = nixpkgs-vulnix.vulnix;
87 | };
88 | }
89 | );
90 | }
91 |
--------------------------------------------------------------------------------
/src/Bootstrap/Data/Bootstrappable/BuildNix.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 |
3 | -- |
4 | -- Copyright : (c) Crown Copyright GCHQ
5 | -- Description : The code for build.nix
6 | module Bootstrap.Data.Bootstrappable.BuildNix (BuildNix (unBuildNix), buildNixFor) where
7 |
8 | import Bootstrap.Data.Bootstrappable
9 | ( Bootstrappable (bootstrapContent, bootstrapName, bootstrapReason),
10 | bootstrapContentNix,
11 | )
12 | import Bootstrap.Data.ProjectName (ProjectName)
13 | import Bootstrap.Data.ProjectType
14 | ( JavaOptions (JavaOptions),
15 | ProjectType
16 | ( Elm,
17 | Go,
18 | Haskell,
19 | Java,
20 | Minimal,
21 | Node,
22 | Python,
23 | Rust
24 | ),
25 | SetUpGoBuild (SetUpGoBuild),
26 | SetUpJavaBuild (SetUpJavaBuild),
27 | haskellOptionsRequireBuild,
28 | )
29 | import Bootstrap.Nix.Expr (Expr (EFunc), FunctionArgs (FASet), IsNixExpr (toNixExpr), nix)
30 | import Bootstrap.Nix.Expr.ReproducibleBuild
31 | ( ReproducibleBuildExpr (ReproducibleBuildExpr, rbeExpr, rbeRequirements),
32 | reproducibleBuildRequirementIdentifier,
33 | )
34 | import Bootstrap.Nix.Expr.ReproducibleBuild.Go (reproducibleGoBuild)
35 | import Bootstrap.Nix.Expr.ReproducibleBuild.Haskell (reproducibleHaskellBuild)
36 | import Bootstrap.Nix.Expr.ReproducibleBuild.Java (reproducibleJavaBuild)
37 | import Bootstrap.Nix.Expr.ReproducibleBuild.Rust (reproducibleRustBuild)
38 |
39 | -- | A separate nix file defining reproducible builds for flake projects,
40 | -- to save it all being kept in flake.nix
41 | newtype BuildNix = BuildNix {unBuildNix :: ReproducibleBuildExpr}
42 | deriving stock (Eq, Show)
43 |
44 | instance Bootstrappable BuildNix where
45 | bootstrapName = const "nix/build.nix"
46 | bootstrapReason = const "This configures your reproducible project builds."
47 | bootstrapContent = bootstrapContentNix
48 |
49 | instance IsNixExpr BuildNix where
50 | toNixExpr (BuildNix ReproducibleBuildExpr {..}) =
51 | EFunc
52 | (FASet $ reproducibleBuildRequirementIdentifier <$> rbeRequirements)
53 | rbeExpr
54 |
55 | -- | Gives a `BuildNix` (or `Nothing`) as appropriate for the project details
56 | -- given.
57 | buildNixFor :: ProjectName -> ProjectType -> Maybe BuildNix
58 | buildNixFor flakeNixProjectName flakeNixProjectType =
59 | BuildNix
60 | <$> buildExprFor SrcDirParent flakeNixProjectName flakeNixProjectType
61 |
62 | -- | The source directory for the build
63 | data SrcDir
64 | = -- | src = ./.;
65 | SrcDirCurrent
66 | | -- | src = ../.;
67 | SrcDirParent
68 |
69 | srcDirExpr :: SrcDir -> Expr
70 | srcDirExpr = \case
71 | SrcDirCurrent -> [nix|./.|]
72 | SrcDirParent -> [nix|../.|]
73 |
74 | buildExprFor :: SrcDir -> ProjectName -> ProjectType -> Maybe ReproducibleBuildExpr
75 | buildExprFor srcDir projectName = \case
76 | Minimal -> Nothing
77 | Elm _ -> Nothing
78 | Haskell haskellOptions
79 | | haskellOptionsRequireBuild haskellOptions ->
80 | Just . reproducibleHaskellBuild projectName $ srcDirExpr srcDir
81 | Haskell _ -> Nothing
82 | Node _ -> Nothing
83 | Go (SetUpGoBuild True) -> Just $ reproducibleGoBuild projectName
84 | Go _ -> Nothing
85 | Java (JavaOptions _ _ (SetUpJavaBuild artefactId) jdk) -> Just $ reproducibleJavaBuild projectName artefactId jdk
86 | Java _ -> Nothing
87 | Python _ -> Nothing
88 | Rust -> Just . reproducibleRustBuild $ srcDirExpr srcDir
89 |
--------------------------------------------------------------------------------
/src/Bootstrap/Unix.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE TemplateHaskell #-}
2 |
3 | -- | Description : Defines utilities for working with command-line tools
4 | -- Copyright : (c) Crown Copyright GCHQ
5 | module Bootstrap.Unix (alejandra, git, runCommand, which, whoami) where
6 |
7 | import Bootstrap.Monad (MonadBootstrap)
8 | import Bootstrap.Terminal (putErrorLn)
9 | import Control.Exception (IOException)
10 | import Control.Monad.Catch (try)
11 | import qualified Data.Text as T
12 | import GHC.IO.Exception (userError)
13 | import GHC.IO.Handle (hGetContents)
14 | import System.IO.Silently (hSilence)
15 | import System.Process
16 | ( CreateProcess (std_err, std_in, std_out),
17 | StdStream (CreatePipe, UseHandle),
18 | createProcess,
19 | proc,
20 | readProcess,
21 | )
22 | import qualified System.Which as Which
23 |
24 | -- | Finds and runs git, returning the result of the command with the given args.
25 | --
26 | -- Shows an error message and exits with failure status (diverges)
27 | -- if git is not available on the system.
28 | git :: (MonadBootstrap m) => [String] -> m (Either IOException String)
29 | git args =
30 | which "git" >>= \case
31 | Just g -> runCommand g args
32 | Nothing -> do
33 | putErrorLn "Git does not appear to be installed. Please install it and then re-run nix-bootstrap."
34 | exitFailure
35 |
36 | -- | Runs alejandra against the given nix expression
37 | alejandra :: (MonadIO m) => String -> ExceptT IOException m String
38 | alejandra expr = do
39 | -- hExpr is the pipe from printf with the correctly sanitised (for terminal use)
40 | -- expression in it
41 | (_, hExpr, _, _) <-
42 | ExceptT
43 | . liftIO
44 | . try
45 | $ createProcess (proc "printf" ["%s", expr]) {std_out = CreatePipe}
46 | case hExpr of
47 | Just hExpr' -> do
48 | -- hFormatted is stdout from alejandra
49 | (_, hFormatted, hError, _) <-
50 | ExceptT
51 | . liftIO
52 | . try
53 | $ createProcess
54 | (proc $(Which.staticWhich "alejandra") [])
55 | { std_err = CreatePipe,
56 | std_in = UseHandle hExpr',
57 | std_out = CreatePipe
58 | }
59 | case hFormatted of
60 | Just h -> do
61 | formatted <- ExceptT . liftIO . try $ hGetContents h
62 | if not (null formatted)
63 | then pure formatted
64 | else case hError of
65 | Just hErr -> ExceptT (Left . userError <$> liftIO (hGetContents hErr))
66 | Nothing -> hoistEither (Left $ userError "hError was Nothing; should not happen")
67 | Nothing -> hoistEither (Left $ userError "hFormatted was Nothing; should not happen")
68 | Nothing -> hoistEither (Left $ userError "hExpr was Nothing; should not happen")
69 |
70 | -- | Gets the path to the requested executable on the system using `which`
71 | which :: (MonadIO m) => FilePath -> m (Maybe FilePath)
72 | which = liftIO . Which.which
73 |
74 | -- | Gets the name of the current user using `whoami`
75 | whoami :: (MonadIO m) => m (Either IOException FilePath)
76 | whoami = (toString . T.strip . toText) <<$>> runCommand "whoami" []
77 |
78 | -- | Runs the given command, silencing (and capturing) output
79 | runCommand :: (MonadIO m) => FilePath -> [String] -> m (Either IOException String)
80 | runCommand binaryPath args =
81 | liftIO
82 | . try
83 | . hSilence [stdout, stderr]
84 | $ readProcess binaryPath args ""
85 |
--------------------------------------------------------------------------------
/test/Bootstrap/Data/Bootstrappable/EnvrcSpec.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | module Bootstrap.Data.Bootstrappable.EnvrcSpec (spec) where
5 |
6 | import Bootstrap.Data.Bootstrappable (Bootstrappable (bootstrapContent))
7 | import Bootstrap.Data.Bootstrappable.Envrc (Envrc (Envrc))
8 | import Bootstrap.Data.PreCommitHook (PreCommitHooksConfig (PreCommitHooksConfig))
9 | import Test.Hspec (Spec, describe, it)
10 | import Test.Hspec.Expectations.Pretty (shouldBe)
11 | import Text.RawString.QQ (r)
12 |
13 | spec :: Spec
14 | spec = describe ".envrc rendering" do
15 | it "render correctly without a shell hook" do
16 | bootstrapContent (Envrc $ PreCommitHooksConfig False)
17 | >>= ( `shouldBe`
18 | Right
19 | [r|direnv version 2.23.0 || exit 1
20 | if [ $(nix-env --version | grep -oE '[0-9]+\.[0-9]+' | head -n1 | sed 's/\./000/') -lt 20004 ]; then
21 | echo 'This project is set up to work with Nix Flakes, which your version of nix doesn'"'"'t support.'
22 | echo 'Please upgrade your nix version to at least 2.4 to continue.'
23 | exit 1
24 | fi
25 | if ! nix show-config --extra-experimental-features nix-command | grep experimental-features | grep flakes 1>/dev/null 2>&1; then
26 | printf '\033[31m'
27 | echo 'This project is set up to work with Nix Flakes, which you don'"'"'t currently have enabled.'
28 | echo 'Please enable flakes by following the instructions at https://nixos.wiki/wiki/flakes#Installing_flakes'
29 | printf '\033[0m'
30 | exit 1
31 | fi
32 | if ! nix show-config 1>/dev/null 2>&1; then
33 | printf '\033[31m'
34 | echo 'This project is set up to work with Nix Flakes, which you don'"'"'t currently have enabled.'
35 | echo 'Specifically, the "nix-command" option is missing from your nix experimental-features configuration.'
36 | echo 'Please enable flakes by following the instructions at https://nixos.wiki/wiki/flakes#Installing_flakes'
37 | printf '\033[0m'
38 | exit 1
39 | fi
40 |
41 | use flake
42 | |]
43 | )
44 | it "renders correctly with a shell hook" do
45 | bootstrapContent (Envrc $ PreCommitHooksConfig True)
46 | >>= ( `shouldBe`
47 | Right
48 | [r|direnv version 2.23.0 || exit 1
49 | if [ $(nix-env --version | grep -oE '[0-9]+\.[0-9]+' | head -n1 | sed 's/\./000/') -lt 20004 ]; then
50 | echo 'This project is set up to work with Nix Flakes, which your version of nix doesn'"'"'t support.'
51 | echo 'Please upgrade your nix version to at least 2.4 to continue.'
52 | exit 1
53 | fi
54 | if ! nix show-config --extra-experimental-features nix-command | grep experimental-features | grep flakes 1>/dev/null 2>&1; then
55 | printf '\033[31m'
56 | echo 'This project is set up to work with Nix Flakes, which you don'"'"'t currently have enabled.'
57 | echo 'Please enable flakes by following the instructions at https://nixos.wiki/wiki/flakes#Installing_flakes'
58 | printf '\033[0m'
59 | exit 1
60 | fi
61 | if ! nix show-config 1>/dev/null 2>&1; then
62 | printf '\033[31m'
63 | echo 'This project is set up to work with Nix Flakes, which you don'"'"'t currently have enabled.'
64 | echo 'Specifically, the "nix-command" option is missing from your nix experimental-features configuration.'
65 | echo 'Please enable flakes by following the instructions at https://nixos.wiki/wiki/flakes#Installing_flakes'
66 | printf '\033[0m'
67 | exit 1
68 | fi
69 |
70 | use flake
71 | eval "$shellHook"
72 | |]
73 | )
74 |
--------------------------------------------------------------------------------
/src/Bootstrap/Data/Bootstrappable/BootstrapState.hs:
--------------------------------------------------------------------------------
1 | -- | Copyright : (c) Crown Copyright GCHQ
2 | module Bootstrap.Data.Bootstrappable.BootstrapState
3 | {-# DEPRECATED "Use Bootstrap.Data.Config instead." #-}
4 | ( BootstrapState
5 | ( stateVersion,
6 | stateProjectName,
7 | stateProjectType,
8 | statePreCommitHooksConfig,
9 | stateContinuousIntegrationConfig,
10 | stateDevContainerConfig,
11 | stateUseFlakes
12 | ),
13 | bootstrapStateCodec,
14 | bootstrapStateFor,
15 | unBootstrapVersion,
16 | BootstrapVersion,
17 | bootstrapStateFileName,
18 | )
19 | where
20 |
21 | import Bootstrap.Data.Bootstrappable (Bootstrappable (bootstrapContent, bootstrapName, bootstrapReason))
22 | import Bootstrap.Data.ContinuousIntegration (ContinuousIntegrationConfig, continuousIntegrationConfigCodec)
23 | import Bootstrap.Data.DevContainer (DevContainerConfig, devContainerConfigCodec)
24 | import Bootstrap.Data.PreCommitHook (PreCommitHooksConfig, preCommitHooksConfigCodec)
25 | import Bootstrap.Data.ProjectName (ProjectName)
26 | import qualified Bootstrap.Data.ProjectName as ProjectName
27 | import Bootstrap.Data.ProjectType (ProjectTypeV2, projectTypeCodec)
28 | import Data.Version (showVersion)
29 | import Paths_nix_bootstrap (version)
30 | import Toml (TomlCodec, (.=))
31 | import qualified Toml
32 |
33 | data BootstrapState = BootstrapState
34 | { stateVersion :: BootstrapVersion,
35 | stateProjectName :: ProjectName,
36 | stateProjectType :: ProjectTypeV2,
37 | statePreCommitHooksConfig :: PreCommitHooksConfig,
38 | stateContinuousIntegrationConfig :: ContinuousIntegrationConfig,
39 | stateDevContainerConfig :: DevContainerConfig,
40 | stateUseFlakes :: Bool
41 | }
42 | deriving stock (Eq, Show)
43 |
44 | instance Bootstrappable BootstrapState where
45 | bootstrapName = const bootstrapStateFileName
46 | bootstrapReason = const "This holds nix-bootstrap's configuration to ensure upgrades are reliable."
47 | bootstrapContent state = pure . Right $ introComment <> Toml.encode bootstrapStateCodec state
48 | where
49 | introComment :: Text
50 | introComment =
51 | unlines
52 | [ "# This file was generated by nix-bootstrap.",
53 | "# It should be checked into version control.",
54 | "# It is used to aid migration between nix-bootstrap versions and preserve idempotence.",
55 | ""
56 | ]
57 |
58 | bootstrapStateFileName :: FilePath
59 | bootstrapStateFileName = ".nix-bootstrap.toml"
60 |
61 | bootstrapStateCodec :: TomlCodec BootstrapState
62 | bootstrapStateCodec =
63 | BootstrapState
64 | <$> Toml.diwrap (Toml.string "version") .= stateVersion
65 | <*> Toml.match ProjectName.tomlBiMap "projectName" .= stateProjectName
66 | <*> projectTypeCodec .= stateProjectType
67 | <*> preCommitHooksConfigCodec .= statePreCommitHooksConfig
68 | <*> continuousIntegrationConfigCodec .= stateContinuousIntegrationConfig
69 | <*> devContainerConfigCodec .= stateDevContainerConfig
70 | <*> Toml.diwrap (Toml.bool "useFlakes") .= stateUseFlakes
71 |
72 | newtype BootstrapVersion = BootstrapVersion {unBootstrapVersion :: String}
73 | deriving stock (Eq, Show)
74 |
75 | bootstrapStateFor ::
76 | ProjectName ->
77 | ProjectTypeV2 ->
78 | PreCommitHooksConfig ->
79 | ContinuousIntegrationConfig ->
80 | DevContainerConfig ->
81 | Bool ->
82 | BootstrapState
83 | bootstrapStateFor = BootstrapState (BootstrapVersion (showVersion version))
84 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to make participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | - Using welcoming and inclusive language
18 | - Being respectful of differing viewpoints and experiences
19 | - Gracefully accepting constructive criticism
20 | - Focusing on what is best for the community
21 | - Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | - The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | - Trolling, insulting/derogatory comments, and personal or political attacks
28 | - Public or private harassment
29 | - Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | - Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies within all project spaces, and it also applies when
49 | an individual is representing the project or its community in public spaces.
50 | Examples of representing a project or community include using an official
51 | project e-mail address, posting via an official social media account, or acting
52 | as an appointed representative at an online or offline event. Representation of
53 | a project may be further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at [oss@gchq.gov.uk](oss@gchq.gov.uk). All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/src/Bootstrap/Nix/Expr/BuildInputs.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 | {-# LANGUAGE TemplateHaskellQuotes #-}
3 |
4 | -- | Copyright : (c) Crown Copyright GCHQ
5 | module Bootstrap.Nix.Expr.BuildInputs (BuildInputSpec (..), buildInputsBindings) where
6 |
7 | import Bootstrap.Data.PreCommitHook
8 | ( PreCommitHooksConfig (unPreCommitHooksConfig),
9 | )
10 | import Bootstrap.Data.ProjectType
11 | ( HasProjectSuperType (projectSuperType),
12 | ProjectSuperType (PSTPython),
13 | )
14 | import Bootstrap.Nix.Expr
15 | ( Binding,
16 | Expr (EGrouping, EList, EWith),
17 | IsNixExpr (toNixExpr),
18 | nix,
19 | nixbinding,
20 | nixproperty,
21 | (|++),
22 | (|=),
23 | )
24 |
25 | data BuildInputGroup
26 | = BIGNixpkgs [Expr]
27 | | BIGOther [Expr]
28 | | BIGPreCommitHooks
29 | | BIGPythonPackages
30 | | BIGNativeNixpkgsInputs [Expr]
31 |
32 | instance IsNixExpr BuildInputGroup where
33 | toNixExpr = \case
34 | BIGNixpkgs buildInputs -> EWith [nix|nixpkgs|] $ EList buildInputs
35 | BIGOther otherPackages -> EList otherPackages
36 | BIGPreCommitHooks -> [nix|preCommitHooks.tools|]
37 | BIGPythonPackages -> [nix|[pythonPackages]|]
38 | BIGNativeNixpkgsInputs nativeBuildInputs -> EWith [nix|nixpkgs|] $ EList nativeBuildInputs
39 |
40 | -- | Whether this group must be wrapped with brackets to be concatenated
41 | requiresGrouping :: BuildInputGroup -> Bool
42 | requiresGrouping = \case
43 | BIGNixpkgs _ -> True
44 | BIGOther _ -> False
45 | BIGPreCommitHooks -> False
46 | BIGPythonPackages -> False
47 | BIGNativeNixpkgsInputs _ -> True
48 |
49 | data BuildInputSpec projectType = BuildInputSpec
50 | { bisNixpkgsPackages :: [Expr],
51 | bisOtherPackages :: [Expr],
52 | bisPreCommitHooksConfig :: PreCommitHooksConfig,
53 | bisProjectType :: projectType,
54 | bisNativeNixpkgsPackages :: [Expr]
55 | }
56 |
57 | buildInputsBindings :: (HasProjectSuperType t) => BuildInputSpec t -> [Binding]
58 | buildInputsBindings spec@BuildInputSpec {bisNativeNixpkgsPackages} =
59 | catMaybes
60 | [ case buildInputGroupExprs of
61 | [] ->
62 | Just
63 | [nixbinding|buildInputs = [
64 | # Insert any dependencies that should exist in the dev shell environment here
65 | ];|]
66 | [buildInputGroupExpr1] ->
67 | Just $ [nixproperty|buildInputs|] |= buildInputGroupExpr1
68 | (buildInputGroupExpr1 : otherBuildInputGroupExprs) ->
69 | Just $ [nixproperty|buildInputs|] |= foldr (|++) buildInputGroupExpr1 otherBuildInputGroupExprs,
70 | if null bisNativeNixpkgsPackages
71 | then Nothing
72 | else Just $ [nixproperty|nativeBuildInputs|] |= groupToExpr (BIGNativeNixpkgsInputs bisNativeNixpkgsPackages)
73 | ]
74 | where
75 | buildInputGroups :: [BuildInputGroup]
76 | buildInputGroups = buildInputGroupsFor spec
77 | groupPackageSets :: Bool
78 | groupPackageSets = length buildInputGroups > 1
79 | groupToExpr :: BuildInputGroup -> Expr
80 | groupToExpr g = (if groupPackageSets && requiresGrouping g then EGrouping else id) (toNixExpr g)
81 | buildInputGroupExprs :: [Expr]
82 | buildInputGroupExprs = groupToExpr <$> buildInputGroups
83 |
84 | buildInputGroupsFor :: (HasProjectSuperType t) => BuildInputSpec t -> [BuildInputGroup]
85 | buildInputGroupsFor BuildInputSpec {..} =
86 | catMaybes
87 | [ if null bisNixpkgsPackages then Nothing else Just (BIGNixpkgs bisNixpkgsPackages),
88 | if unPreCommitHooksConfig bisPreCommitHooksConfig then Just BIGPreCommitHooks else Nothing,
89 | if projectSuperType bisProjectType == PSTPython then Just BIGPythonPackages else Nothing,
90 | case bisOtherPackages of
91 | [] -> Nothing
92 | otherPackages -> Just $ BIGOther otherPackages
93 | ]
94 |
--------------------------------------------------------------------------------
/test/Bootstrap/Data/Bootstrappable/BootstrapStateSpec.hs:
--------------------------------------------------------------------------------
1 | {-# OPTIONS_GHC -Wno-deprecations #-}
2 | {-# OPTIONS_GHC -Wno-orphans #-}
3 |
4 | -- | Copyright : (c) Crown Copyright GCHQ
5 | module Bootstrap.Data.Bootstrappable.BootstrapStateSpec (spec) where
6 |
7 | import Bootstrap.Data.Bootstrappable (Bootstrappable (bootstrapContent))
8 | import Bootstrap.Data.Bootstrappable.BootstrapState (BootstrapState, bootstrapStateCodec, bootstrapStateFor)
9 | import Bootstrap.Data.ContinuousIntegration (ContinuousIntegrationConfig (ContinuousIntegrationConfig))
10 | import Bootstrap.Data.DevContainer (DevContainerConfig (DevContainerConfig))
11 | import Bootstrap.Data.PreCommitHook (PreCommitHooksConfig (PreCommitHooksConfig))
12 | import Bootstrap.Data.ProjectName (mkProjectName)
13 | import Bootstrap.Data.ProjectType
14 | ( JavaOptionsV2 (JavaOptionsV2),
15 | ProjectTypeV2
16 | ( PTV2Go,
17 | PTV2Java,
18 | PTV2Minimal,
19 | PTV2Node,
20 | PTV2Python
21 | ),
22 | )
23 | import Bootstrap.Data.ProjectTypeSpec ()
24 | import Data.Version (showVersion)
25 | import Paths_nix_bootstrap (version)
26 | import qualified Relude.Unsafe as Unsafe
27 | import Test.Hspec (Spec, describe, it)
28 | import Test.Hspec.Expectations.Pretty (shouldBe)
29 | import Test.Hspec.QuickCheck (prop)
30 | import Test.QuickCheck
31 | ( Arbitrary (arbitrary),
32 | arbitraryBoundedEnum,
33 | generate,
34 | oneof,
35 | )
36 | import Test.Util (tomlRoundtripTest)
37 |
38 | instance Arbitrary BootstrapState where
39 | arbitrary = do
40 | let projectName = Unsafe.fromJust $ mkProjectName "test-project"
41 | projectType <- arbitrary
42 | preCommitHooksConfig <- PreCommitHooksConfig <$> arbitrary
43 | devContainerConfig <- DevContainerConfig <$> arbitrary
44 | ciConfig <- ContinuousIntegrationConfig <$> arbitrary
45 | bootstrapStateFor projectName projectType preCommitHooksConfig ciConfig devContainerConfig <$> arbitrary
46 |
47 | instance Arbitrary ProjectTypeV2 where
48 | arbitrary =
49 | oneof
50 | [ pure PTV2Minimal,
51 | PTV2Node <$> arbitraryBoundedEnum,
52 | PTV2Go <$> arbitraryBoundedEnum,
53 | PTV2Java <$> (JavaOptionsV2 <$> arbitraryBoundedEnum <*> arbitraryBoundedEnum <*> arbitrary),
54 | PTV2Python <$> arbitraryBoundedEnum
55 | ]
56 |
57 | spec :: Spec
58 | spec = describe ".nix-bootstrap.toml rendering" do
59 | it "renders correctly" do
60 | let projectName = Unsafe.fromJust $ mkProjectName "test-project"
61 | nodePackageManager <- generate Test.QuickCheck.arbitraryBoundedEnum
62 | bootstrapContent
63 | ( bootstrapStateFor
64 | projectName
65 | (PTV2Node nodePackageManager)
66 | (PreCommitHooksConfig True)
67 | (ContinuousIntegrationConfig True)
68 | (DevContainerConfig True)
69 | False
70 | )
71 | >>= ( `shouldBe`
72 | Right
73 | ( unlines
74 | [ "# This file was generated by nix-bootstrap.",
75 | "# It should be checked into version control.",
76 | "# It is used to aid migration between nix-bootstrap versions and preserve idempotence.",
77 | "",
78 | "projectName = \"test-project\"",
79 | "setUpContinuousIntegration = true",
80 | "setUpDevContainer = true",
81 | "setUpPreCommitHooks = true",
82 | "useFlakes = false",
83 | "version = \"" <> toText (showVersion version) <> "\"",
84 | "",
85 | "[projectType]",
86 | " nodePackageManager = \"" <> show nodePackageManager <> "\"",
87 | " projectSuperType = \"PSTNode\""
88 | ]
89 | )
90 | )
91 | prop "roundtrips" $ tomlRoundtripTest bootstrapStateCodec
92 |
--------------------------------------------------------------------------------
/nix/pre-commit-hooks.nix:
--------------------------------------------------------------------------------
1 | # © Crown Copyright GCHQ
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | {
15 | alejandra,
16 | nixpkgs,
17 | pre-commit-hooks-lib,
18 | src,
19 | system,
20 | vulnix,
21 | }: let
22 | # Function to make a set of pre-commit hooks
23 | makeHooks = hooks:
24 | pre-commit-hooks-lib.lib.${system}.run {
25 | inherit hooks src;
26 | };
27 | # Hooks which don't depend on running in a dev environment
28 | pureHooks = {
29 | alejandra = {
30 | enable = true;
31 | entry = nixpkgs.lib.mkOverride 0 "${alejandra}/bin/alejandra";
32 | };
33 | hlint = {
34 | enable = true;
35 | entry = nixpkgs.lib.mkOverride 0 "${pre-commit-hooks-lib.packages.${system}.hlint}/bin/hlint -XNoCPP";
36 | };
37 | hpack = {
38 | enable = true;
39 | entry = nixpkgs.lib.mkOverride 0 "${
40 | nixpkgs.writeShellScriptBin
41 | "hpack-override"
42 | "(${pre-commit-hooks-lib.packages.${system}.hpack}/bin/hpack)"
43 | }/bin/hpack-override";
44 | };
45 | ormolu.enable = true;
46 | prettier = {
47 | enable = true;
48 | excludes = [".pnp.*" ".pre-commit-config.yaml"];
49 | };
50 | shellcheck = {
51 | enable = true;
52 | entry = nixpkgs.lib.mkOverride 0 "${
53 | pre-commit-hooks-lib.packages."${system}".shellcheck
54 | }/bin/shellcheck -x";
55 | types_or = ["shell"];
56 | excludes = [".envrc"];
57 | };
58 | };
59 | # Hooks which can run on pre-commit but not in CI
60 | impureHooks = {
61 | hpack-with-version-bump = {
62 | enable = true;
63 | entry = "${
64 | nixpkgs.writeShellScriptBin "hpack-with-version-bump.sh" ''
65 | set -e
66 | (${nixpkgs.git}/bin/git fetch --all && \
67 | ${nixpkgs.git}/bin/git diff "$(git describe --tags --abbrev=0)" -- package.yaml | ${nixpkgs.gnugrep}/bin/grep version) \
68 | || (echo "You must bump the nix-bootstrap version number in package.yaml!" && set -e && exit 1)
69 | (${pre-commit-hooks-lib.packages.${system}.hpack}/bin/hpack)
70 | ''
71 | }/bin/hpack-with-version-bump.sh";
72 | files = ".*";
73 | name = "Update config and version";
74 | pass_filenames = false;
75 | };
76 | # Prefixed with z_ to make it run last
77 | z_build_nix_bootstrap = {
78 | enable = true;
79 | entry = "nix build";
80 | files = "\\.(cabal|hs|nix|yaml)$";
81 | name = "build";
82 | pass_filenames = false;
83 | };
84 | z_vulnerabilities = {
85 | enable = true;
86 | entry = "${
87 | nixpkgs.writeShellScriptBin
88 | "check-for-vulnerabilities"
89 | "${vulnix}/bin/vulnix -C -w vulnerability-whitelist.toml result/"
90 | }/bin/check-for-vulnerabilities";
91 | files = "nix-bootstrap";
92 | name = "check-for-vulnerabilities";
93 | pass_filenames = false;
94 | };
95 | };
96 | in {
97 | allHooks = makeHooks (builtins.removeAttrs (pureHooks // impureHooks) ["hpack"]);
98 | pureHooks = makeHooks pureHooks;
99 | tools =
100 | [alejandra vulnix]
101 | ++ (with pre-commit-hooks-lib.packages.${system}; [
102 | hlint
103 | hpack
104 | hpack-dir
105 | ormolu
106 | prettier
107 | shellcheck
108 | ]);
109 | }
110 |
--------------------------------------------------------------------------------
/src/Bootstrap/Nix/Expr/ReproducibleBuild/Java.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 | {-# LANGUAGE TemplateHaskellQuotes #-}
3 |
4 | -- | Copyright : (c) Crown Copyright GCHQ
5 | module Bootstrap.Nix.Expr.ReproducibleBuild.Java (reproducibleJavaBuild) where
6 |
7 | import Bootstrap.Data.ProjectName (ProjectName (unProjectName))
8 | import Bootstrap.Data.ProjectType (ArtefactId (unArtefactId), JdkPackage (GraalVM, OpenJDK))
9 | import Bootstrap.Nix.Expr
10 | ( Expr (ELetIn, EList, ELit),
11 | Literal (LString),
12 | nix,
13 | nixbinding,
14 | nixproperty,
15 | (|=),
16 | )
17 | import Bootstrap.Nix.Expr.ReproducibleBuild
18 | ( ReproducibleBuildExpr (ReproducibleBuildExpr),
19 | ReproducibleBuildRequirement (RBRNixpkgs),
20 | )
21 |
22 | reproducibleJavaBuild :: ProjectName -> ArtefactId -> JdkPackage -> ReproducibleBuildExpr
23 | reproducibleJavaBuild projectName artefactId jdk =
24 | ReproducibleBuildExpr
25 | ( ELetIn
26 | ( ([nixproperty|projectName|] |= ELit (LString $ unProjectName projectName))
27 | :| [ [nixproperty|artefactId|] |= ELit (LString $ unArtefactId artefactId),
28 | [nixproperty|buildInputs|] |= EList (mvnOverride jdk),
29 | [nixbinding|
30 | repository = nixpkgs.stdenv.mkDerivation {
31 | inherit buildInputs;
32 | name = "${projectName}-repository";
33 | src = ./.;
34 | buildPhase = "mvn package -Dmaven.repo.local=$out";
35 | # keep only *.{pom,jar,sha1,nbm} and delete all ephemeral files with lastModified timestamps inside
36 | installPhase = ''
37 | find $out -type f \\
38 | -name \\*.lastUpdated -or \\
39 | -name resolver-status.properties -or \\
40 | -name _remote.repositories \\
41 | -delete
42 | '';
43 | dontFixup = true;
44 | outputHashAlgo = "sha256";
45 | outputHashMode = "recursive";
46 | # This hash will need updating when changing your dependencies; the correct
47 | # hash will be displayed when the build fails if so.
48 | outputHash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
49 | };|],
50 | [nixbinding|
51 | builtJar = nixpkgs.stdenv.mkDerivation rec {
52 | inherit buildInputs;
53 | pname = projectName;
54 | version = "0.0.1-SNAPSHOT";
55 | src = ./.;
56 | buildPhase = ''
57 | echo "Using repository ${repository}"
58 | mvn --offline -Dmaven.repo.local=${repository} package;
59 | '';
60 | installPhase = ''
61 | install -Dm644 target/${artefactId}-${version}.jar $out/${projectName}
62 | '';
63 | };|],
64 | [nixbinding|
65 | fromImage = nixpkgs.dockerTools.pullImage {
66 | imageName = "eclipse-temurin";
67 | imageDigest = "sha256:4cc7dfdfb7837f35c3820bcfbc5f666521364e2198960322848ab7d3e2ca3e88";
68 | finalImageName = "eclipse-temurin";
69 | finalImageTag = "17.0.4.1_1-jre";
70 | sha256 = "sha256-OIib6PQJc1xX+Xu2xtaFEo/jZtxypkg8Y9RFMcJf39w=";
71 | };|]
72 | ]
73 | )
74 | [nix|
75 | nixpkgs.dockerTools.buildLayeredImage {
76 | inherit fromImage;
77 | name = projectName;
78 | enableFakechroot = true;
79 | fakeRootCommands = ''
80 | cp ${builtJar}/${projectName} /${projectName}
81 | chown root:root /${projectName}
82 | chmod 774 /${projectName}
83 | '';
84 | config = {
85 | Entrypoint = ["/bin/sh" "-c" "java $JAVA_OPTS -jar /${projectName}"];
86 | };
87 | }|]
88 | )
89 | (one RBRNixpkgs)
90 |
91 | mvnOverride :: JdkPackage -> [Expr]
92 | mvnOverride = \case
93 | OpenJDK -> [[nix|nixpkgs.maven|]]
94 | GraalVM -> [[nix|(nixpkgs.maven.override { jdk = nixpkgs.graalvm-ce; })|]]
95 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to nix-bootstrap
2 |
3 | This file details the steps necessary in order to contribute to the project,
4 | providing guidance and development practices to follow in order to make successful contributions.
5 |
6 | ## Contributions
7 |
8 | Please read our [Code of Conduct](CODE_OF_CONDUCT.md) before contributing to this project.
9 |
10 | Before your contributions can be accepted, you must:
11 |
12 | - Sign the [GCHQ Contributor Licence Agreement](https://cla-assistant.io/gchq/nix-bootstrap).
13 | - Push your changes to new branch.
14 | - Submit a pull request.
15 |
16 | ## Coding Practices
17 |
18 | ### Versioning
19 |
20 | `nix-bootstrap` broadly follows the [PVP Versioning Specification](https://pvp.haskell.org/),
21 | but with some modifications on the basis it's an end product rather than a library:
22 |
23 | - The flagship version (first component) is bumped only at major, defining releases.
24 | - The major version (second component) is bumped whenever the user input changes - this is
25 | analagous to a breaking change.
26 | - The minor version (third component) is bumped for all other features.
27 | - The patch version (last component) is bumped for all other changes which don't affect
28 | nix-bootstrap's feature set.
29 |
30 | The version number must be updated for every (set of) commits added to `main`.
31 |
32 | ### Comments
33 |
34 | - When considering adding a comment, first consider if the code could be better explained by
35 | restructuring it (e.g. by pulling out a function)
36 | - If the code cannot be made clear by restructuring, add a comment to explain it
37 | - Haddock comments should be added to all exposed items in libraries
38 |
39 | ### Property Ordering
40 |
41 | For the purpose of this point, a logical order is one of the following:
42 |
43 | - Something universally recognisable, like alphabetical
44 | - Something recognisable in context, such as CSS properties being grouped into width and height,
45 | flex options, colour options etc.
46 | - In this case, the groups should each be logically ordered
47 |
48 | Based on those definitions:
49 |
50 | - In cases where a logical order of properties is already being used, that order must be followed
51 | unless changing to a new logical order is justified
52 | - In cases where no logical order is given already, developers touching that property set should re-order the set to fit a logical order
53 | - New property sets must be given a logical order
54 |
55 | ### Use of Type Systems
56 |
57 | - Type systems must be leveraged where possible to ensure code correctness
58 |
59 | ### Identifiers / Variable Names
60 |
61 | Identifiers must be:
62 |
63 | - Consistent
64 | - Meaningful
65 | - Given in line with the idioms/best practice of the language/toolchain in which it's written
66 |
67 | ## Version Control Practices
68 |
69 | ### Branching Strategies
70 |
71 | - Rebase and Fast Forward will be the default merge strategy for PRs
72 | - No merge commits from the base branch (e.g. `main`) will be permitted into feature branches.
73 | If conflicts arise, these should instead be solved by rebasing the feature branch onto the HEAD of the base branch
74 |
75 | ### Branch Naming
76 |
77 | - The main branch will be called `main`
78 | - Branch names should consist of the ticket number (where a ticket exists) and a short description
79 | - Words in branch names should be separated by dashes
80 |
81 | ### Commit Conventions
82 |
83 | Commits should be made as frequently as possible, and each commit should successfully build
84 |
85 | ### Commit Messages
86 |
87 | Commit messages must:
88 |
89 | - Be short where possible
90 | - Be meaningful
91 | - Have no reliance on external context (e.g. should not say things like "make PR changes")
92 |
93 | ### Pull Requests
94 |
95 | Pull requests will undergo an in depth review by a project contributor to check the code
96 | changes are compliant with our coding style.
97 |
98 | This is a community so please be respectful of other members - offer encouragement, support and suggestions.
99 |
100 | Please agree to the [GCHQ OSS Contributor License Agreement](https://cla-assistant.io/gchq/nix-bootstrap)
101 | before submitting a pull request.
102 |
--------------------------------------------------------------------------------
/test/Bootstrap/Data/Bootstrappable/NixPreCommitHookConfigSpec.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE QuasiQuotes #-}
2 |
3 | -- | Copyright : (c) Crown Copyright GCHQ
4 | module Bootstrap.Data.Bootstrappable.NixPreCommitHookConfigSpec (spec) where
5 |
6 | import Bootstrap.Data.Bootstrappable (Bootstrappable (bootstrapContent))
7 | import Bootstrap.Data.Bootstrappable.NixPreCommitHookConfig
8 | ( nixPreCommitHookConfigFor,
9 | )
10 | import Bootstrap.Data.ProjectType
11 | ( InstallLombok (InstallLombok),
12 | InstallMinishift (InstallMinishift),
13 | JavaOptions (JavaOptions),
14 | JdkPackage (OpenJDK),
15 | NodePackageManager (NPM),
16 | ProjectType (Go, Java, Node),
17 | SetUpGoBuild (SetUpGoBuild),
18 | SetUpJavaBuild (NoJavaBuild),
19 | )
20 | import Test.Hspec (Spec, describe, it)
21 | import Test.Hspec.Expectations.Pretty (shouldBe)
22 | import Text.RawString.QQ (r)
23 |
24 | spec :: Spec
25 | spec = describe "nix/pre-commit-hooks.nix rendering" do
26 | it "renders correctly when using default Go hooks" do
27 | bootstrapContent (nixPreCommitHookConfigFor $ Go $ SetUpGoBuild False)
28 | >>= ( `shouldBe`
29 | Right
30 | [r|{
31 | pre-commit-hooks-lib,
32 | system,
33 | nixpkgs,
34 | }: let
35 | # Function to make a set of pre-commit hooks
36 | makeHooks = hooks:
37 | pre-commit-hooks-lib.lib.${system}.run {
38 | inherit hooks;
39 | src = ../.;
40 | };
41 | # Hooks which don't depend on running in a dev environment
42 | pureHooks = {
43 | alejandra.enable = true;
44 | go-fmt = {
45 | enable = true;
46 | entry = "${nixpkgs.go}/bin/go fmt";
47 | files = "\\.go$";
48 | pass_filenames = false;
49 | };
50 | go-test = {
51 | enable = true;
52 | entry = "${nixpkgs.go}/bin/go test";
53 | files = "\\.(go|mod)$";
54 | pass_filenames = false;
55 | };
56 | };
57 | # Hooks which can run on pre-commit but not in CI
58 | impureHooks = {};
59 | in {
60 | pureHooks = makeHooks pureHooks;
61 | allHooks = makeHooks (pureHooks // impureHooks);
62 | tools = (with pre-commit-hooks-lib.packages.${system}; [alejandra]) ++ (with nixpkgs; [go]);
63 | }
64 | |]
65 | )
66 | it "renders correctly when using default NPM hooks" do
67 | bootstrapContent (nixPreCommitHookConfigFor $ Node NPM)
68 | >>= ( `shouldBe`
69 | Right
70 | [r|{
71 | pre-commit-hooks-lib,
72 | system,
73 | }: let
74 | # Function to make a set of pre-commit hooks
75 | makeHooks = hooks:
76 | pre-commit-hooks-lib.lib.${system}.run {
77 | inherit hooks;
78 | src = ../.;
79 | };
80 | # Hooks which don't depend on running in a dev environment
81 | pureHooks = {
82 | alejandra.enable = true;
83 | prettier.enable = true;
84 | };
85 | # Hooks which can run on pre-commit but not in CI
86 | impureHooks = {};
87 | in {
88 | pureHooks = makeHooks pureHooks;
89 | allHooks = makeHooks (pureHooks // impureHooks);
90 | tools = with pre-commit-hooks-lib.packages.${system}; [alejandra prettier];
91 | }
92 | |]
93 | )
94 | it "renders correctly when using default Java hooks" do
95 | bootstrapContent
96 | ( nixPreCommitHookConfigFor
97 | (Java $ JavaOptions (InstallMinishift False) (InstallLombok False) NoJavaBuild OpenJDK)
98 | )
99 | >>= ( `shouldBe`
100 | Right
101 | [r|{
102 | pre-commit-hooks-lib,
103 | system,
104 | nixpkgs,
105 | }: let
106 | # Function to make a set of pre-commit hooks
107 | makeHooks = hooks:
108 | pre-commit-hooks-lib.lib.${system}.run {
109 | inherit hooks;
110 | src = ../.;
111 | };
112 | # Hooks which don't depend on running in a dev environment
113 | pureHooks = {
114 | alejandra.enable = true;
115 | google-java-format = {
116 | enable = true;
117 | entry = "${nixpkgs.google-java-format}/bin/google-java-format -i";
118 | files = "\\.java$";
119 | pass_filenames = true;
120 | };
121 | };
122 | # Hooks which can run on pre-commit but not in CI
123 | impureHooks = {};
124 | in {
125 | pureHooks = makeHooks pureHooks;
126 | allHooks = makeHooks (pureHooks // impureHooks);
127 | tools = (with pre-commit-hooks-lib.packages.${system}; [alejandra]) ++ (with nixpkgs; [google-java-format]);
128 | }
129 | |]
130 | )
131 |
--------------------------------------------------------------------------------
/src/Bootstrap/Data/Bootstrappable/GitlabCIConfig.hs:
--------------------------------------------------------------------------------
1 | -- | Copyright : (c) Crown Copyright GCHQ
2 | module Bootstrap.Data.Bootstrappable.GitlabCIConfig
3 | ( GitlabCIConfig,
4 | gitlabCIConfigFor,
5 | )
6 | where
7 |
8 | import Bootstrap.Data.Bootstrappable
9 | ( Bootstrappable
10 | ( bootstrapContent,
11 | bootstrapName,
12 | bootstrapReason
13 | ),
14 | )
15 | import Bootstrap.Data.Bootstrappable.NixPreCommitHookConfig
16 | ( NixPreCommitHookConfig (nixPreCommitHookConfigImpureHookCommand),
17 | )
18 | import Bootstrap.Data.ContinuousIntegration
19 | ( ContinuousIntegrationConfig (ContinuousIntegrationConfig),
20 | )
21 | import Bootstrap.Data.ProjectType
22 | ( ElmMode (ElmModeBare, ElmModeNode),
23 | ElmOptions (ElmOptions, elmOptionElmMode),
24 | JavaOptions (JavaOptions),
25 | NodePackageManager (NPM, PNPm, Yarn),
26 | ProjectType (Elm, Go, Java),
27 | SetUpGoBuild (SetUpGoBuild),
28 | SetUpJavaBuild (SetUpJavaBuild),
29 | )
30 |
31 | data GitlabCIConfig = GitlabCIConfig
32 | { gitlabCIConfigProjectType :: ProjectType,
33 | gitlabCIConfigPreCommitHooksConfig :: Maybe NixPreCommitHookConfig
34 | }
35 |
36 | instance Bootstrappable GitlabCIConfig where
37 | bootstrapName = const ".gitlab-ci.yml"
38 | bootstrapReason = const "This file sets up GitLab CI."
39 | bootstrapContent GitlabCIConfig {..} = do
40 | pure . Right . unlines $
41 | [ "image: nixos/nix@sha256:473a2b527958665554806aea24d0131bacec46d23af09fef4598eeab331850fa",
42 | "",
43 | "default:",
44 | " before_script:",
45 | " - nix-channel --add https://nixos.org/channels/nixpkgs-unstable nixpkgs",
46 | " - nix-channel --update",
47 | " - nix-env -iA nixpkgs.bash nixpkgs.openssh",
48 | " - echo \"experimental-features = nix-command flakes\" >> /etc/nix/nix.conf",
49 | "",
50 | case nixPreCommitHookConfigImpureHookCommand <$> gitlabCIConfigPreCommitHooksConfig of
51 | Just c | c /= "echo ok" -> "check-dev-environment-and-run-impure-hooks:"
52 | _ -> "check-dev-environment:",
53 | " stage: build",
54 | " script:",
55 | commandInShell $
56 | maybe
57 | "echo ok"
58 | nixPreCommitHookConfigImpureHookCommand
59 | gitlabCIConfigPreCommitHooksConfig
60 | ]
61 | <> ( if isJust gitlabCIConfigPreCommitHooksConfig
62 | then
63 | [ "",
64 | "pre-commit-check:",
65 | " stage: build",
66 | " script:",
67 | " - nix flake check"
68 | ]
69 | else []
70 | )
71 | <> case gitlabCIConfigProjectType of
72 | Elm opts -> elmSiteJob opts
73 | Go (SetUpGoBuild True) -> buildJob
74 | Java (JavaOptions _ _ (SetUpJavaBuild _) _) -> buildJob
75 | _ -> []
76 | where
77 | buildJob :: [Text]
78 | buildJob =
79 | [ "",
80 | "build:",
81 | " stage: build",
82 | " script:",
83 | " - nix build"
84 | ]
85 | elmSiteJob :: ElmOptions -> [Text]
86 | elmSiteJob ElmOptions {elmOptionElmMode} =
87 | [ "",
88 | "build-site:",
89 | " stage: build",
90 | " script:"
91 | ]
92 | <> ( commandInShell <$> case elmOptionElmMode of
93 | ElmModeBare -> ["elm make src/Main.elm"]
94 | ElmModeNode packageManager ->
95 | [ nodePackageManagerInstall packageManager,
96 | runWithPackageManager packageManager <> "build"
97 | ]
98 | )
99 | commandInShell :: Text -> Text
100 | commandInShell = (" - nix develop -c " <>)
101 | nodePackageManagerInstall :: NodePackageManager -> Text
102 | nodePackageManagerInstall = \case
103 | NPM -> "npm ci"
104 | Yarn -> "yarn install --frozen-lockfile"
105 | PNPm -> "pnpm install --frozen-lockfile"
106 | runWithPackageManager :: NodePackageManager -> Text
107 | runWithPackageManager =
108 | (<> " run ") . \case
109 | NPM -> "npm"
110 | Yarn -> "yarn"
111 | PNPm -> "pnpm"
112 |
113 | gitlabCIConfigFor ::
114 | ContinuousIntegrationConfig ->
115 | ProjectType ->
116 | Maybe NixPreCommitHookConfig ->
117 | Maybe GitlabCIConfig
118 | gitlabCIConfigFor (ContinuousIntegrationConfig False) _ _ = Nothing
119 | gitlabCIConfigFor (ContinuousIntegrationConfig True) t p =
120 | Just $ GitlabCIConfig t p
121 |
--------------------------------------------------------------------------------
/src/Bootstrap/Cli.hs:
--------------------------------------------------------------------------------
1 | -- | Copyright : (c) Crown Copyright GCHQ
2 | module Bootstrap.Cli
3 | ( Command (..),
4 | ErrorMessage,
5 | RunConfig (..),
6 | allowDirtyFlagName,
7 | fromScratchFlagName,
8 | parseCommand,
9 | showHelp,
10 | )
11 | where
12 |
13 | import Bootstrap.Data.DevContainer (DevContainerConfig (DevContainerConfig))
14 | import Bootstrap.Monad (MonadBootstrap)
15 | import Bootstrap.Terminal (putErrorLn, withAttribute, withAttributes)
16 | import Data.Char (isSpace)
17 | import qualified Data.Text as T
18 | import System.Console.GetOpt as GetOpt
19 | ( ArgDescr (NoArg),
20 | ArgOrder (Permute),
21 | OptDescr (Option),
22 | getOpt,
23 | usageInfo,
24 | )
25 | import System.Terminal
26 | ( MonadColorPrinter (blue, foreground),
27 | MonadFormattingPrinter (bold),
28 | MonadPrinter (putLn),
29 | putText,
30 | putTextLn,
31 | yellow,
32 | )
33 |
34 | data Command
35 | = CommandHelp [ErrorMessage]
36 | | CommandRun RunConfig
37 | | CommandVersion
38 |
39 | newtype ErrorMessage = ErrorMessage {unErrorMessage :: Text}
40 |
41 | data RunConfig = RunConfig
42 | { rcAllowDirty :: Bool,
43 | rcFromScratch :: Bool,
44 | rcNonInteractive :: Bool,
45 | rcWithDevContainer :: Maybe DevContainerConfig
46 | }
47 |
48 | parseCommand :: (MonadIO m) => m Command
49 | parseCommand = do
50 | args <- getArgs
51 | let (options, _, errors) = getOpt Permute cliOptions args
52 | if not $ null errors
53 | then pure . CommandHelp $ ErrorMessage . T.strip . toText <$> errors
54 | else
55 | if Help `elem` options
56 | then pure $ CommandHelp []
57 | else
58 | if Version `elem` options
59 | then pure CommandVersion
60 | else do
61 | let rcAllowDirty = AllowDirty `elem` options
62 | rcFromScratch = FromScratch `elem` options
63 | rcNonInteractive = NonInteractive `elem` options
64 | rcWithDevContainer =
65 | if WithDevContainer `elem` options
66 | then Just (DevContainerConfig True)
67 | else Nothing
68 | pure $ CommandRun RunConfig {..}
69 |
70 | data Flag
71 | = AllowDirty
72 | | FromScratch
73 | | Help
74 | | NonInteractive
75 | | Version
76 | | WithDevContainer
77 | deriving stock (Eq)
78 |
79 | allowDirtyFlagName :: (IsString s) => s
80 | allowDirtyFlagName = "allow-dirty"
81 |
82 | fromScratchFlagName :: (IsString s) => s
83 | fromScratchFlagName = "from-scratch"
84 |
85 | cliOptions :: [OptDescr Flag]
86 | cliOptions =
87 | [ GetOpt.Option [] [allowDirtyFlagName] (NoArg AllowDirty) "Allow nix-bootstrap to run even if the current state of the git repo is dirty.",
88 | GetOpt.Option [] [fromScratchFlagName] (NoArg FromScratch) $
89 | "Ignore any previous nix-bootstrap state files and re-prompt for every "
90 | <> "configuration question. Note that this doesn't delete files which are not overwritten in the new configuration.",
91 | GetOpt.Option [] ["help"] (NoArg Help) "Print the program information and usage.",
92 | GetOpt.Option [] ["non-interactive"] (NoArg NonInteractive) "When working from an existing state file, don't require any user input.",
93 | GetOpt.Option [] ["version"] (NoArg Version) "Print the program version.",
94 | GetOpt.Option [] ["with-devcontainer"] (NoArg WithDevContainer) "Make sure nix-bootstrap sets up a VSCode DevContainer."
95 | ]
96 |
97 | showHelp :: (MonadBootstrap m) => [ErrorMessage] -> m ()
98 | showHelp errors = do
99 | mapM_ (putErrorLn . ("Error: " <>) . unErrorMessage) errors
100 | withAttributes [bold, foreground blue] $ putTextLn "Usage: nix-bootstrap [OPTION]..."
101 | let usageInfoLines = lines . toText $ usageInfo "Generate infrastructure for common types of project.\n" cliOptions
102 | case uncons usageInfoLines of
103 | Nothing -> putLn
104 | Just (firstLine, restOfLines) -> do
105 | putTextLn firstLine
106 | putLn
107 | forM_ restOfLines \line -> do
108 | case words line of
109 | [] -> pass
110 | (w : ws) -> do
111 | putText $ T.takeWhile isSpace line
112 | withAttribute bold $ putText w
113 | let postFlagSpacing =
114 | T.takeWhile isSpace
115 | . T.dropWhile (not . isSpace)
116 | $ T.dropWhile isSpace line
117 | putText postFlagSpacing
118 | forM_ ws \word -> do
119 | if word == "(EXPERIMENTAL)"
120 | then withAttributes [bold, foreground yellow] $ putText word
121 | else putText word
122 | putText " "
123 | putLn
124 |
--------------------------------------------------------------------------------