summary refs log tree commit diff
path: root/pkgs/development/mobile/titaniumenv/build-app.nix
blob: 9e19afb244293e15e7616532ef53169b1a606490 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
{stdenv, composeAndroidPackages, composeXcodeWrapper, titaniumsdk, titanium, alloy, jdk, python, nodejs, which, file}:
{ name, src, preBuild ? "", target, tiVersion ? null
, release ? false, androidKeyStore ? null, androidKeyAlias ? null, androidKeyStorePassword ? null
, iosMobileProvisioningProfile ? null, iosCertificateName ? null, iosCertificate ? null, iosCertificatePassword ? null, iosVersion ? "12.1", iosBuildStore ? false
, enableWirelessDistribution ? false, installURL ? null
, xcodeBaseDir ? "/Applications/Xcode.app"
, androidsdkArgs ? {}
, xcodewrapperArgs ? {}
, ...
}@args:

assert (release && target == "android") -> androidKeyStore != null && androidKeyAlias != null && androidKeyStorePassword != null;
assert (release && target == "iphone") -> iosMobileProvisioningProfile != null && iosCertificateName != null && iosCertificate != null && iosCertificatePassword != null;
assert enableWirelessDistribution -> installURL != null;

let
  realAndroidsdkArgs = {
    platformVersions = [ "28" ];
  } // androidsdkArgs;

  androidsdk = (composeAndroidPackages realAndroidsdkArgs).androidsdk;

  realXcodewrapperArgs = {
    inherit xcodeBaseDir;
  } // xcodewrapperArgs;

  xcodewrapper = composeXcodeWrapper xcodewrapperArgs;

  deleteKeychain = ''
    if [ -f $HOME/lock-keychain ]
    then
        security default-keychain -s login.keychain
        security delete-keychain $keychainName
        rm -f $HOME/lock-keychain
    fi
  '';

  extraArgs = removeAttrs args [ "name" "preRebuild" "androidsdkArgs" "xcodewrapperArgs" ];
in
stdenv.mkDerivation ({
  name = stdenv.lib.replaceChars [" "] [""] name;

  buildInputs = [ nodejs titanium alloy python which file jdk ];

  buildPhase = ''
    ${preBuild}

    ${stdenv.lib.optionalString stdenv.isDarwin ''
      # Hack that provides a writable alloy package on macOS. Without it the build fails because of a file permission error.
      alloy=$(dirname $(type -p alloy))/..
      cp -rv $alloy/* alloy
      chmod -R u+w alloy
      export PATH=$(pwd)/alloy/bin:$PATH
    ''}

    export HOME=${if target == "iphone" then "/Users/$(whoami)" else "$TMPDIR"}

    ${stdenv.lib.optionalString (tiVersion != null) ''
      # Replace titanium version by the provided one
      sed -i -e "s|<sdk-version>[0-9a-zA-Z\.]*</sdk-version>|<sdk-version>${tiVersion}</sdk-version>|" tiapp.xml
    ''}

    # Simulate a login
    mkdir -p $HOME/.titanium
    cat > $HOME/.titanium/auth_session.json <<EOF
    { "loggedIn": true }
    EOF

    # Configure the paths to the Titanium SDK and modules
    echo "{}" > $TMPDIR/config.json
    titanium --config-file $TMPDIR/config.json --no-colors config sdk.defaultInstallLocation ${titaniumsdk}
    titanium --config-file $TMPDIR/config.json --no-colors config paths.modules ${titaniumsdk}

    mkdir -p $out

    ${if target == "android" then ''
      titanium config --config-file $TMPDIR/config.json --no-colors android.sdkPath ${androidsdk}/libexec/android-sdk

      export PATH=${androidsdk}/libexec/android-sdk/tools:$(echo ${androidsdk}/libexec/android-sdk/build-tools/android-*):$PATH
      export GRADLE_USER_HOME=$TMPDIR/gradle

      ${if release then ''
        ${stdenv.lib.optionalString stdenv.isDarwin ''
          # Signing the app does not work with OpenJDK on macOS, use host SDK instead
          export JAVA_HOME="$(/usr/libexec/java_home -v 1.8)"
        ''}
        titanium build --config-file $TMPDIR/config.json --no-colors --force --platform android --target dist-playstore --keystore ${androidKeyStore} --alias "${androidKeyAlias}" --store-password "${androidKeyStorePassword}" --output-dir $out
      '' else ''
        titanium build --config-file $TMPDIR/config.json --no-colors --force --platform android --target emulator --build-only -B foo --output $out
      ''}
    ''
    else if target == "iphone" then ''
      # Be sure that the Xcode wrapper has priority over everything else.
      # When using buildInputs this does not seem to be the case.
      export PATH=${xcodewrapper}/bin:$PATH

      # Configure the path to Xcode
      titanium --config-file $TMPDIR/config.json --no-colors config paths.xcode ${xcodeBaseDir}

      # Link the modules folder
      if [ ! -e modules ]
      then
          ln -s ${titaniumsdk}/modules modules
          createdModulesSymlink=1
      fi

      ${if release then ''
        # Create a keychain with the component hash name (should always be unique)
        export keychainName=$(basename $out)

        security create-keychain -p "" $keychainName
        security default-keychain -s $keychainName
        security unlock-keychain -p "" $keychainName
        security import ${iosCertificate} -k $keychainName -P "${iosCertificatePassword}" -A
        security set-key-partition-list -S apple-tool:,apple: -s -k "" $keychainName
        provisioningId=$(grep UUID -A1 -a ${iosMobileProvisioningProfile} | grep -o "[-A-Za-z0-9]\{36\}")

        # Ensure that the requested provisioning profile can be found

        if [ ! -f "$HOME/Library/MobileDevice/Provisioning Profiles/$provisioningId.mobileprovision" ]
        then
            mkdir -p "$HOME/Library/MobileDevice/Provisioning Profiles"
            cp ${iosMobileProvisioningProfile} "$HOME/Library/MobileDevice/Provisioning Profiles/$provisioningId.mobileprovision"
        fi

        # Take precautions to prevent concurrent builds blocking the keychain
        while [ -f $HOME/lock-keychain ]
        do
            echo "Keychain locked, waiting for a couple of seconds, or remove $HOME/lock-keychain to unblock..."
            sleep 3
        done

        touch $HOME/lock-keychain

        security default-keychain -s $keychainName

        # Do the actual build
        titanium build --config-file $TMPDIR/config.json --force --no-colors --platform ios --target ${if iosBuildStore then "dist-appstore" else "dist-adhoc"} --pp-uuid $provisioningId --distribution-name "${iosCertificateName}" --keychain $HOME/Library/Keychains/$keychainName-db --device-family universal --ios-version ${iosVersion} --output-dir $out

        # Remove our generated keychain
        ${deleteKeychain}
      '' else ''
        # Copy all sources to the output store directory.
        # Why? Debug application include *.js files, which are symlinked into their
        # sources. If they are not copied, we have dangling references to the
        # temp folder.

        cp -av * $out
        cd $out

        # Execute the build
        titanium build --config-file $TMPDIR/config.json --force --no-colors --platform ios --target simulator --build-only --device-family universal --ios-version ${iosVersion} --output-dir $out

        # Remove the modules symlink
        if [ "$createdModulesSymlink" = "1" ]
        then
            rm $out/modules
        fi
      ''}
    '' else throw "Target: ${target} is not supported!"}
  '';

  installPhase = ''
    ${if target == "android" then ''
      ${if release then ""
      else ''
        cp "$(ls build/android/bin/*.apk | grep -v '\-unsigned.apk')" $out
      ''}

      mkdir -p $out/nix-support
      echo "file binary-dist \"$(ls $out/*.apk)\"" > $out/nix-support/hydra-build-products
    ''
    else if target == "iphone" then
      if release then ''
        mkdir -p $out/nix-support
        echo "file binary-dist \"$(echo $out/*.ipa)\"" > $out/nix-support/hydra-build-products

        ${stdenv.lib.optionalString enableWirelessDistribution ''
          appname="$(basename $out/*.ipa .ipa)"
          bundleId=$(grep '<id>[a-zA-Z0-9.]*</id>' tiapp.xml | sed -e 's|<id>||' -e 's|</id>||' -e 's/ //g')
          version=$(grep '<version>[a-zA-Z0-9.]*</version>' tiapp.xml | sed -e 's|<version>||' -e 's|</version>||' -e 's/ //g')

          sed -e "s|@INSTALL_URL@|${installURL}?bundleId=$bundleId\&amp;version=$version\&amp;title=$appname|" ${../xcodeenv/install.html.template} > "$out/$appname.html"
          echo "doc install \"$out/$appname.html\"" >> $out/nix-support/hydra-build-products
        ''}
      ''
      else ""
    else throw "Target: ${target} is not supported!"}
  '';

  failureHook = stdenv.lib.optionalString (release && target == "iphone") deleteKeychain;
} // extraArgs)