on master help sign in
wip
commit1efa65a678eaafa41dab12a4bb149cf9ea4626aa authorLuigy Leon <luichi.19@gmail.com> dateTue 20 Nov 2018, 17:01 (8 years ago) parent2dcdabee5e5e55
7 files changed+138−48
README.md+9−27 default.nix+6−1 dep/reflex-platform/github.json+2−2 lib/command/obelisk-command.cabal+2 lib/command/src/Obelisk/Command.hs+2−7 lib/command/src/Obelisk/Command/Deploy.hs+116−11 lib/command/src/Obelisk/Command/Nix.hs+1
README.md+9−27
@@ -247,38 +247,20 @@ Now deploy the built apk file to your Android device:
2472471. Connect the device using USB (be sure to confirm any security prompts on the device)
2482481. Run the deploy script: `result-android/bin/deploy`
249249
250−This should copy over and install the application on your device (if you see a "*signatures do not match*" error, simply uninstall the previous app from the device before retrying the deploy). The name of the installed application will be what you have specified for `android.displayName` in the `default.nix`.
251−
252−#### Releasing to Play Store
250+Alternatively, you can deploy from an obelisk deployment directory (a directory generated post `ob deploy init ...` command) using the `ob deploy test android` command.
251+This command will accomplish the following:
253252
254−##### Configure signing
253+1. Create a key store and apk signing key (`android_keystore.jks`)
254+1. Build a Signed Android apk for your application
255+1. Deploy the Signed apk to your connected Android device
255256
256−The previous section would have generated a debug version of the app. In order to build a release version you will need to sign your app. Obelisk can automatically sign the app during build if you provide it with your keystore file in `default.nix`.
257+In the event that you change your key or keystore password, you will have to update your credentials within the JSON object found in `android_keytool_config.json`
257258
258−First, if you do not already have a keystore, create it as follows (for more information, see the [Android documentation](https://developer.android.com/studio/publish/app-signing#signing-manually)):
259−
260−```
261−nix-shell -p androidenv.platformTools --run "keytool -genkey -v -keystore myandroidkey.jks -keyalg RSA -keysize 2048 -validity 10000 -alias myandroidalias"
262−```
259+Additional documentation on java key stores can be found [here] (https://docs.oracle.com/javase/8/docs/technotes/tools/unix/keytool.html)
263260
264−(Be sure to give an appropriate keystore filename and key alias string above.)
265−
266−The `keytool` command will ask you for some details, including a keystore password and a key password (we will use these passwords further below). It will now have created a `myandroidkey.jks` file under the current directory. Move that to somewhere safe, and note down its full path.
267−
268−Now edit your project's `default.nix` and tell Obelisk of your app's keystore file. Your `default.nix` should look like this after the edit:
261+This should copy over and install the application on your device (if you see a "*signatures do not match*" error, simply uninstall the previous app from the device before retrying the deploy). The name of the installed application will be what you have specified for `android.displayName` in the `default.nix`.
269262
270−```nix
271− ...
272− android.applicationId = "com.example.myapp";
273− android.displayName = "My App";
274− android.releaseKey =
275− { storeFile = /path/to/myandroidkey.jks;
276− storePassword = "abcd1234";
277− keyAlias = "myandroidalias";
278− keyPassword = "abcd1234";
279− };
280− ...
281−```
263+#### Releasing to Play Store
282264
283265##### Build a release version
284266
default.nix+6−1
@@ -12,7 +12,6 @@ let
1212 commandRuntimeDeps = pkgs: with pkgs; [
1313 coreutils
1414 git
15− gitAndTools.hub
1615 nix-prefetch-git
1716 openssh
1817 ];
@@ -35,6 +34,12 @@ let
3534 (self: super: let
3635 pkgs = self.callPackage ({ pkgs }: pkgs) {};
3736 in {
37+ hnix = pkgs.haskell.lib.dontCheck (self.callCabal2nix "hnix" (pkgs.fetchFromGitHub {
38+ owner = "haskell-nix";
39+ repo = "hnix";
40+ rev = "42afdc21da5d9e076eab57eaa42bfdde938192b8";
41+ sha256 = "0psw384dx9bw2dp93xrzw8rd9amvcwgzn64jzzwby7sfspj6k349";
42+ }) {});
3843 # Need 8.0.2 build support
3944 # PR: https://github.com/dmwit/universe/pull/33
4045 universe-template = self.callCabal2nix "universe-template" (pkgs.fetchFromGitHub {
dep/reflex-platform/github.json+2−2
@@ -2,6 +2,6 @@
22 "owner": "reflex-frp",
33 "repo": "reflex-platform",
44 "branch": "develop",
5− "rev": "c7221496dfbd8a296eee98bb9c30f81a5022cdf4",
6− "sha256": "0s2knl17hkcig3gp2b5drpr822rlx7bpdni28maykkb4i3fykpag"
5+ "rev": "89a9b09259a8e0681661756bb587a8285927c1e8",
6+ "sha256": "13pja2jy5f8xk2bbywhqaj582f4kqd2ww8j5jd7kk5ldzl6zv8g3"
77}
lib/command/obelisk-command.cabal+2
@@ -23,6 +23,7 @@ library
2323 , github
2424 , here
2525 , hit
26+ , hnix
2627 , hpack
2728 , io-streams
2829 , exceptions
@@ -43,6 +44,7 @@ library
4344 , time
4445 , transformers
4546 , unix
47+ , unordered-containers
4648 , yaml
4749 exposed-modules:
4850 Obelisk.App
lib/command/src/Obelisk/Command.hs+2−7
@@ -143,7 +143,7 @@ deployCommand cfg = hsubparser $ mconcat
143143 where
144144 platformP = hsubparser $ mconcat
145145 [ command "android" $ info (pure (Android, [])) mempty
146− , command "ios" $ info ((,) <$> pure IOS <*> strArgument (metavar "TEAMID" <> help "Your Team ID - found in the Apple developer portal")) mempty
146+ , command "ios" $ info ((,) <$> pure IOS <*> (fmap pure $ strArgument (metavar "TEAMID" <> help "Your Team ID - found in the Apple developer portal"))) mempty
147147 ]
148148
149149 remoteBuilderParser :: Parser (Maybe RemoteBuilder)
@@ -173,9 +173,6 @@ deployInitOpts = DeployInitOpts
173173 <*> flag True False (long "disable-https" <> help "Disable automatic https configuration for the backend")
174174
175175type TeamID = String
176−data PlatformDeployment = Android | IOS
177− deriving (Show)
178−
179176data RemoteBuilder = RemoteBuilder_ObeliskVM
180177 deriving (Eq, Show)
181178
@@ -341,9 +338,7 @@ ob = \case
341338 Nothing -> pure []
342339 Just RemoteBuilder_ObeliskVM -> (:[]) <$> VmBuilder.getNixBuildersArg
343340 DeployCommand_Update -> deployUpdate "."
344− DeployCommand_Test (platform, args) -> deployMobile platform args
345− -- DeployCommand_Test Android -> deployMobile "__android" []
346− -- DeployCommand_Test (IOS teamID) -> deployMobile "__ios" [teamID]
341+ DeployCommand_Test (platform, extraArgs) -> deployMobile platform extraArgs
347342 ObCommand_Run -> inNixShell' $ static run
348343 -- inNixShell ($(mkClosure 'ghcidAction) ())
349344 ObCommand_Thunk tc -> case tc of
lib/command/src/Obelisk/Command/Deploy.hs+116−11
@@ -3,23 +3,34 @@
33{-# LANGUAGE OverloadedStrings #-}
44{-# LANGUAGE ScopedTypeVariables #-}
55{-# LANGUAGE ViewPatterns #-}
6+{-# LANGUAGE DeriveGeneric #-}
7+{-# LANGUAGE TypeApplications #-}
68module Obelisk.Command.Deploy where
79
810import Control.Lens
911import Control.Monad
10−import Control.Monad.Catch (Exception (displayException), MonadThrow, throwM, try)
12+import Control.Monad.Catch (Exception (displayException), MonadThrow, throwM, try, bracket_)
1113import Control.Monad.IO.Class (liftIO)
14+import Data.Aeson (FromJSON, ToJSON, encode, eitherDecode)
1215import Data.Bits
16+import qualified Data.ByteString.Lazy as BSL
1317import Data.Default
18+import qualified Data.HashMap.Strict as HM
1419import qualified Data.Map as Map
20+import Data.Text (Text)
1521import qualified Data.Set as Set
1622import qualified Data.Text as T
1723import qualified Data.Text.IO as T
24+import GHC.Generics
25+import Nix.Convert
26+import Nix.Pretty
27+import Nix.Value
1828import System.Directory
1929import System.Environment (getEnvironment)
2030import System.FilePath
31+import System.IO
2132import System.Posix.Files
22−import System.Process (delegate_ctlc, env, proc)
33+import System.Process (delegate_ctlc, env, proc, cwd)
2334import Text.URI (URI)
2435import qualified Text.URI as URI
2536import Text.URI.Lens
@@ -157,25 +168,119 @@ deployPush deployPath getNixBuilders = do
157168deployUpdate :: MonadObelisk m => FilePath -> m ()
158169deployUpdate deployPath = updateThunkToLatest $ deployPath </> "src"
159170
160−deployMobile :: MonadObelisk m => String -> [String] -> m ()
171+keytoolToAndroidConfig :: KeytoolConfig -> HM.HashMap Text (NValueNF Identity)
172+keytoolToAndroidConfig conf = runIdentity $ do
173+ path <- toValue $ Path $ _keytoolConfig_keystore conf
174+ storepass <- toValue $ T.pack $ _keytoolConfig_storepass conf
175+ alias <- toValue $ T.pack $ _keytoolConfig_alias conf
176+ keypass <- toValue $ T.pack $ _keytoolConfig_keypass conf
177+ return $ HM.fromList
178+ [ ("storeFile", path)
179+ , ("storePassword", storepass)
180+ , ("keyAlias", alias)
181+ , ("keyPassword", keypass)
182+ ]
183+
184+data PlatformDeployment = Android | IOS
185+ deriving (Show, Eq)
186+
187+renderPlatformDeployment :: PlatformDeployment -> String
188+renderPlatformDeployment = \case
189+ Android -> "android"
190+ IOS -> "ios"
191+
192+deployMobile :: MonadObelisk m => PlatformDeployment -> [String] -> m ()
161193deployMobile platform mobileArgs = withProjectRoot "." $ \root -> do
162194 let srcDir = root </> "src"
163195 configDir = root </> "config"
164196 exists <- liftIO $ doesDirectoryExist srcDir
165197 unless exists $ failWith "ob test should be run inside of a deploy directory"
166− -- result <- nixBuildAttrWithCache srcDir $ platform <> ".frontend"
167− let expr = mconcat
168− [ "with (import ", srcDir, " {});"
169− -- , "android.frontend.override (_: { staticSrc = (passthru.__android ", configDir, ").frontend.staticSrc; })"
170− , "ios.frontend.override (_: { staticSrc = (passthru.__ios ", configDir, ").frontend.staticSrc; })"
171− ]
172− target = Target
198+ nixBuildTarget <- case platform of
199+ Android -> do
200+ let keystorePath = root </> "android_keystore.jks"
201+ keytoolConfPath = root </> "android_keytool_config.json"
202+ hasKeystore <- liftIO $ doesFileExist keystorePath
203+ when (not hasKeystore) $ do
204+ -- TODO log instructions for how to modify the keystore
205+ putLog Notice $ "Creating keystore: " <> T.pack keystorePath
206+ putLog Notice "Enter a keystore password: "
207+ keyStorePassword <- liftIO $ withEcho False getLine
208+ putLog Notice "Re-enter the keystore password: "
209+ keyStorePassword' <- liftIO $ withEcho False getLine
210+ unless (keyStorePassword' == keyStorePassword) $ failWith "passwords do not match"
211+ let keyToolConf = KeytoolConfig
212+ { _keytoolConfig_keystore = keystorePath
213+ , _keytoolConfig_alias = "obelisk"
214+ , _keytoolConfig_storepass = keyStorePassword
215+ , _keytoolConfig_keypass = keyStorePassword
216+ }
217+ createKeystore root $ keyToolConf
218+ liftIO $ BSL.writeFile keytoolConfPath $ encode keyToolConf
219+ checkKeytoolConfExist <- liftIO $ doesFileExist keytoolConfPath
220+ unless checkKeytoolConfExist $ failWith "Missing android KeytoolConfig"
221+ keytoolConfContents <- liftIO $ BSL.readFile keytoolConfPath
222+ liftIO $ putStrLn $ show keytoolConfContents
223+ releaseKey <- case eitherDecode keytoolConfContents of
224+ Left err -> failWith $ T.pack err
225+ Right conf -> do
226+ let nvset = toValue @(HM.HashMap Text (NValueNF Identity)) @Identity @(NValueNF Identity) $ keytoolToAndroidConfig conf
227+ return $ printNix $ runIdentity nvset
228+ let expr = mconcat
229+ [ "with (import ", srcDir, " {});"
230+ , "android.frontend.override (drv: { "
231+ , "releaseKey = (if builtins.isNull drv.releaseKey then {} else drv.releaseKey) // " <> releaseKey <> "; "
232+ , "staticSrc = (passthru.__android ", configDir, ").frontend.staticSrc;"
233+ , "})"
234+ ]
235+ return $ Target
236+ { _target_path = Nothing
237+ , _target_attr = Nothing
238+ , _target_expr = Just expr
239+ }
240+ IOS -> do
241+ let expr = mconcat
242+ [ "with (import ", srcDir, " {});"
243+ , "ios.frontend.override (_: { staticSrc = (passthru.__ios ", configDir, ").frontend.staticSrc; })"
244+ ]
245+ return $ Target
173246 { _target_path = Nothing
174247 , _target_attr = Nothing
175248 , _target_expr = Just expr
176249 }
177− result <- nixCmd $ NixCmd_Build $ def & nixBuildConfig_common .~ (def & nixCmdConfig_target .~ target)
250+ result <- nixCmd $ NixCmd_Build $ def
251+ & nixBuildConfig_outLink .~ OutLink_None
252+ & nixCmdConfig_target .~ nixBuildTarget
253+ putLog Notice $ T.pack $ "Your recently built android apk can be found at the following path: " <> (show result)
178254 callProcessAndLogOutput (Notice, Error) $ proc (result </> "bin" </> "deploy") mobileArgs
255+ where
256+ withEcho showEcho f = do
257+ prevEcho <- hGetEcho stdin
258+ bracket_ (hSetEcho stdin showEcho) (hSetEcho stdin prevEcho) f
259+
260+data KeytoolConfig = KeytoolConfig
261+ { _keytoolConfig_keystore :: FilePath
262+ , _keytoolConfig_alias :: String
263+ , _keytoolConfig_storepass :: String
264+ , _keytoolConfig_keypass :: String
265+ } deriving (Show, Generic)
266+
267+instance FromJSON KeytoolConfig
268+instance ToJSON KeytoolConfig
269+
270+createKeystore :: MonadObelisk m => FilePath -> KeytoolConfig -> m ()
271+createKeystore root config = do
272+ let expr = "with (import " <> toImplDir root <> ").reflex-platform.nixpkgs; pkgs.mkShell { buildInputs = [ pkgs.jdk ]; }"
273+ callProcessAndLogOutput (Notice,Notice) $ (proc "nix-shell" ["-E" , expr, "--run" , keytoolCmd]) { cwd = Just root }
274+ where
275+ keytoolCmd = processToShellString "keytool"
276+ [ "-genkeypair", "-noprompt"
277+ , "-keystore", _keytoolConfig_keystore config
278+ , "-keyalg", "RSA", "-keysize", "2048"
279+ , "-validity", "1000000000"
280+ , "-storepass", _keytoolConfig_storepass config
281+ , "-alias", _keytoolConfig_alias config
282+ , "-keypass", _keytoolConfig_keypass config
283+ ]
179284
180285-- | Simplified deployment configuration mechanism. At one point we may revisit this.
181286writeDeployConfig :: MonadObelisk m => FilePath -> FilePath -> String -> m ()
lib/command/src/Obelisk/Command/Nix.hs+1
@@ -181,3 +181,4 @@ nixCmd cmdCfg = withSpinner' ("Running " <> cmd <> desc) (Just $ const $ "Built
181181 [ (" on " <>) <$> path
182182 , (\a -> " [" <> a <> "]") <$> (commonCfg ^. nixCmdConfig_target . target_attr)
183183 ]
184+