nixenvironment 0.0.70 → 0.0.71

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,24 +1,87 @@
1
1
  module Nixenvironment
2
2
  class BuildEnvVarsLoader
3
3
  def self.load
4
- cmd_output = %x[ source \"#{LOAD_BUILD_ENV_VARS_SCRIPT_PATH}\" ].strip!
4
+ build_env_vars = load_last_revision.merge(load_last_build_vars)
5
+
6
+ built_products_dir = build_env_vars[BUILT_PRODUCTS_DIR_KEY]
7
+ app_product = build_env_vars[APP_PRODUCT_KEY]
8
+ ipa_product = build_env_vars[IPA_PRODUCT_KEY]
9
+ app_infoplist_file = build_env_vars['APP_INFOPLIST_FILE']
10
+ embedded_profile = build_env_vars['EMBEDDED_PROFILE']
11
+ monotonic_revision = build_env_vars['MONOTONIC_REVISION']
12
+ sdk = build_env_vars[SDK_NAME_KEY]
13
+
14
+ raise "error: directory '#{built_products_dir}' must exist (build products dir)!" unless File.exist?(built_products_dir)
15
+ raise "error: app product directory '#{app_product}' or ipa file '#{ipa_product}' must exist!" unless File.exist?(app_product) && File.exist?(ipa_product)
16
+ raise "error: file '#{app_infoplist_file}' must exist (app info plist)!" unless File.exist?(app_infoplist_file)
17
+
18
+ if sdk.include?('ios')
19
+ embedded_profile_exist = embedded_profile.present? && File.exist?(embedded_profile)
20
+ embedded_profile_name = embedded_profile_exist ? %x[ /usr/libexec/PlistBuddy -c 'Print :Name' /dev/stdin <<< $(security cms -D -i '#{embedded_profile}') ].strip : 'SIMULATOR'
21
+ end
22
+
23
+ current_app_version = %x[ /usr/libexec/PlistBuddy -c 'Print CFBundleShortVersionString' '#{app_infoplist_file}' ].strip
24
+
25
+ build_env_vars[EMBEDDED_PROFILE_NAME_KEY] = embedded_profile_name
26
+ build_env_vars[CURRENT_APP_VERSION_KEY] = current_app_version
27
+ build_env_vars[CURRENT_BUILD_VERSION_KEY] = monotonic_revision
28
+
29
+ puts
30
+ puts "#{EMBEDDED_PROFILE_NAME_KEY} = #{embedded_profile_name}"
31
+ puts "#{CURRENT_APP_VERSION_KEY} = #{current_app_version}"
32
+ puts "#{CURRENT_BUILD_VERSION_KEY} = #{monotonic_revision}"
33
+ puts
34
+
35
+ build_env_vars
36
+ end
37
+
38
+ def self.load_last_revision
39
+ load_internal(AUTOGENERATED_LAST_REVISION, false)
40
+ end
41
+
42
+ def self.load_last_build_vars
43
+ load_internal(AUTOGENERATED_LAST_BUILD_VARS, false)
44
+ end
45
+
46
+ def self.working_copy_is_clean?
47
+ working_copy_is_clean = load_last_revision['WORKING_COPY_IS_CLEAN'] == '1'
48
+
49
+ if working_copy_is_clean
50
+ puts 'Working copy is clean. Continuing ...'
51
+ else
52
+ puts 'error: working copy must not have local modifications.'
53
+ puts 'You must add following files and folders to .gitignore:'
54
+ Git.status(:porcelain => true)
55
+ end
56
+
57
+ working_copy_is_clean
58
+ end
59
+
60
+ def self.load_internal(file, print_log = true)
61
+ raise "error: file '#{file}' must exist!" unless File.exist?(file)
62
+
5
63
  build_env_vars = {}
6
64
 
7
- vars_list = cmd_output.split(/\n/).reject(&:empty?)
65
+ vars_list = File.read(file).lines.reject(&:empty?)
8
66
  if vars_list.present?
67
+ puts if print_log
68
+
9
69
  vars_to_strip = Hash[vars_list.map { |it| it.split('=', 2) }]
10
70
  vars_to_strip.each do |key, value|
11
- if key.present? && value.present?
71
+ if key.present? && !key.include?(AUTOGENERATED_FILES_HEADER)
12
72
  stripped_key = key.strip
13
- stripped_value = value.strip
14
-
15
- puts "#{stripped_key} : #{stripped_value}"
73
+ stripped_value = value ? value.tr("'", '').strip : 'unknown'
74
+ puts "#{stripped_key} = #{stripped_value}" if print_log
16
75
  build_env_vars[stripped_key] = stripped_value
17
76
  end
18
77
  end
78
+
79
+ puts if print_log
19
80
  end
20
81
 
21
82
  build_env_vars
22
83
  end
84
+
85
+ private_class_method :load_internal
23
86
  end
24
87
  end
@@ -12,15 +12,10 @@ module Nixenvironment
12
12
 
13
13
  # ninbas legacy
14
14
  BUILD_SCRIPTS_PATH = File.expand_path('../../../legacy', __FILE__)
15
- SAVE_REVISION_SCRIPT_PATH = File.join(BUILD_SCRIPTS_PATH, 'SaveRevision.sh')
16
15
  UNITY_BUILD_ANDROID_SCRIPT_PATH = File.join(BUILD_SCRIPTS_PATH, 'UnityBuildAndroid.py')
17
- LOAD_BUILD_ENV_VARS_SCRIPT_PATH = File.join(BUILD_SCRIPTS_PATH, 'LoadBuildEnvVars.sh')
18
16
  CODE_COVERAGE_REPORT_SCRIPT_PATH = File.join(BUILD_SCRIPTS_PATH, 'GenerateCodeCoverageForXCTests.sh')
19
17
  CODE_DUPLICATION_REPORT_SCRIPT_PATH = File.join(BUILD_SCRIPTS_PATH, 'GenerateCodeDuplicationReport.sh')
20
- MAKE_TAG_SCRIPT_PATH = File.join(BUILD_SCRIPTS_PATH, 'MakeTag.sh')
21
- CLEAN_WORKING_COPY_SCRIPT_PATH = File.join(BUILD_SCRIPTS_PATH, 'CleanWorkingCopy.sh')
22
18
  DEPLOY_SCRIPT_PATH = File.join(BUILD_SCRIPTS_PATH, 'Deploy.sh')
23
- DEPLOY_IPA_SCRIPT_PATH = File.join(BUILD_SCRIPTS_PATH, 'DeployIPA.sh')
24
19
  DEPLOY_APK_SCRIPT_PATH = File.join(BUILD_SCRIPTS_PATH, 'DeployAPK.py')
25
20
 
26
21
  # utils
@@ -34,11 +29,20 @@ module Nixenvironment
34
29
 
35
30
  # file extensions
36
31
  APP_EXT = '.app'
32
+ IPA_EXT = '.ipa'
37
33
  APPEX_EXT = '.appex'
38
34
  DSYM_EXT = '.dSYM'
39
35
  ZIP_EXT = '.zip'
40
36
  GIT_EXT = '.git'
41
37
 
38
+ # autogenerated
39
+ AUTOGENERATED_FILES_HEADER = '### AUTOGENERATED BY Nixenvironment; DO NOT EDIT ###'
40
+ AUTOGENERATED_LAST_REVISION = '_last_revision.sh'
41
+ AUTOGENERATED_LAST_BUILD_VARS = '_last_build_vars.sh'
42
+ AUTOGENERATED_COVERAGE = 'coverage.xml'
43
+ AUTOGENERATED_DUPLICATION = 'duplication.xml'
44
+ AUTOGENERATED_TEST_RESULTS = 'test-results/'
45
+
42
46
  # watch, extensions, widgets
43
47
  WATCHKIT_APP_KEY = 'WatchKit App'
44
48
  WATCHKIT_EXTENSION_KEY = 'WatchKit Extension'
@@ -0,0 +1,129 @@
1
+ require 'net/scp'
2
+ require 'colorize'
3
+
4
+ module Nixenvironment
5
+ class Deployer
6
+ def self.deploy(deploy_host, deploy_dir, deploy_user, deploy_password, deploy_itunesconnect_user, deliver_deploy)
7
+ build_env_vars = BuildEnvVarsLoader.load
8
+ raise 'Error! Working copy is not clean!' unless BuildEnvVarsLoader.working_copy_is_clean?
9
+
10
+ ipa_product = build_env_vars[IPA_PRODUCT_KEY]
11
+ ipa_product_resigned_device = build_env_vars['IPA_PRODUCT_RESIGNED_DEVICE']
12
+ ipa_product_resigned_adhoc = build_env_vars['IPA_PRODUCT_RESIGNED_ADHOC']
13
+ ipa_product_resigned_appstore = build_env_vars['IPA_PRODUCT_RESIGNED_APPSTORE']
14
+
15
+ ipa_bundle_id = build_env_vars[IPA_BUNDLE_ID_KEY]
16
+ ipa_bundle_id_resigned_device = build_env_vars['IPA_BUNDLE_ID_RESIGNED_DEVICE']
17
+ ipa_bundle_id_resigned_adhoc = build_env_vars['IPA_BUNDLE_ID_RESIGNED_ADHOC']
18
+ ipa_bundle_id_resigned_appstore = build_env_vars['IPA_BUNDLE_ID_RESIGNED_APPSTORE']
19
+
20
+ current_app_version = build_env_vars[CURRENT_APP_VERSION_KEY]
21
+ current_build_version = build_env_vars[CURRENT_BUILD_VERSION_KEY]
22
+
23
+ name_for_deployment = build_env_vars[NAME_FOR_DEPLOYMENT_KEY]
24
+ name_for_deployment_resigned_device = build_env_vars['NAME_FOR_DEPLOYMENT_RESIGNED_DEVICE']
25
+ name_for_deployment_resigned_adhoc = build_env_vars['NAME_FOR_DEPLOYMENT_RESIGNED_ADHOC']
26
+ name_for_deployment_resigned_appstore = build_env_vars['NAME_FOR_DEPLOYMENT_RESIGNED_APPSTORE']
27
+
28
+ executable_name = build_env_vars[EXECUTABLE_NAME_KEY]
29
+ app_dsym = build_env_vars[APP_DSYM_KEY]
30
+ sdk = build_env_vars[SDK_NAME_KEY]
31
+
32
+ ipa_count = 0
33
+
34
+ if ipa_product.present? && File.exist?(ipa_product)
35
+ ipa_count += 1
36
+ deploy_internal(deploy_host, deploy_dir, deploy_user, deploy_password, deploy_itunesconnect_user, deliver_deploy,
37
+ ipa_bundle_id, ipa_product, current_app_version, current_build_version, name_for_deployment, executable_name, app_dsym, sdk)
38
+ end
39
+
40
+ if ipa_product_resigned_device.present? && File.exist?(ipa_product_resigned_device)
41
+ ipa_count += 1
42
+ ipa_bundle_id_resigned_device = ipa_bundle_id if ipa_bundle_id_resigned_device.blank?
43
+ deploy_internal(deploy_host, deploy_dir, deploy_user, deploy_password, deploy_itunesconnect_user, deliver_deploy,
44
+ ipa_bundle_id_resigned_device, ipa_product_resigned_device, current_app_version, current_build_version, name_for_deployment_resigned_device,
45
+ executable_name, app_dsym, sdk)
46
+ end
47
+
48
+ if ipa_product_resigned_adhoc.present? && File.exist?(ipa_product_resigned_adhoc)
49
+ ipa_count += 1
50
+ ipa_bundle_id_resigned_adhoc = ipa_bundle_id if ipa_bundle_id_resigned_adhoc.blank?
51
+ deploy_internal(deploy_host, deploy_dir, deploy_user, deploy_password, deploy_itunesconnect_user, deliver_deploy,
52
+ ipa_bundle_id_resigned_adhoc, ipa_product_resigned_adhoc, current_app_version, current_build_version, name_for_deployment_resigned_adhoc,
53
+ executable_name, app_dsym, sdk)
54
+ end
55
+
56
+ if ipa_product_resigned_appstore.present? && File.exist?(ipa_product_resigned_appstore)
57
+ ipa_count += 1
58
+ ipa_bundle_id_resigned_appstore = ipa_bundle_id if ipa_bundle_id_resigned_appstore.blank?
59
+ deploy_internal(deploy_host, deploy_dir, deploy_user, deploy_password, deploy_itunesconnect_user, deliver_deploy,
60
+ ipa_bundle_id_resigned_appstore, ipa_product_resigned_appstore, current_app_version, current_build_version, name_for_deployment_resigned_appstore,
61
+ executable_name, app_dsym, sdk)
62
+ end
63
+
64
+ raise 'Nothing to upload!' if ipa_count == 0
65
+ end
66
+
67
+ def self.deploy_internal(deploy_host, deploy_dir, deploy_user, deploy_password, deploy_itunesconnect_user, deliver_deploy,
68
+ ipa_bundle_id, ipa_product, current_app_version, current_build_version, name_for_deployment, executable_name, app_dsym, sdk)
69
+ is_macos_build = sdk.include?('macos')
70
+
71
+ local_path_to_app = File.join(Dir.tmpdir, ipa_bundle_id)
72
+ local_path_to_build = File.join(local_path_to_app, "v.#{current_app_version}_#{current_build_version}")
73
+ configuration_full_path = File.join(local_path_to_build, name_for_deployment)
74
+ destination_ipa_product = File.join(configuration_full_path, executable_name + (is_macos_build ? ZIP_EXT : IPA_EXT))
75
+
76
+ FileUtils.rm_rf(local_path_to_build)
77
+ FileUtils.mkdir_p(configuration_full_path)
78
+ FileUtils.cp_r(ipa_product, destination_ipa_product)
79
+
80
+ unless is_macos_build
81
+ if deploy_itunesconnect_user.present?
82
+ puts
83
+ print 'Starting pilot ...'.blue
84
+ puts
85
+
86
+ skip_submission = deliver_deploy ? '' : '--skip_submission'
87
+ system("pilot upload -i '#{ipa_product}' -u '#{deploy_itunesconnect_user}' #{skip_submission}")
88
+ end
89
+
90
+ # zip dsym
91
+ dsym_path = executable_name + APP_EXT + DSYM_EXT
92
+ zipped_dsym_path = File.join(configuration_full_path, dsym_path + ZIP_EXT)
93
+
94
+ if File.exist?(app_dsym)
95
+ Dir.chdir(File.dirname(app_dsym)) do
96
+ system("/usr/bin/zip --symlinks --verbose --recurse-paths '#{zipped_dsym_path}' '#{File.basename(app_dsym)}'")
97
+ end
98
+ end
99
+ end
100
+
101
+ FileUtils.chmod_R(0775, local_path_to_app)
102
+
103
+ scp(deploy_host, deploy_dir, deploy_user, deploy_password, local_path_to_app)
104
+
105
+ FileUtils.rm_rf(local_path_to_app)
106
+ end
107
+
108
+ def self.scp(deploy_host, deploy_dir, deploy_user, deploy_password, local_path_to_app)
109
+ puts
110
+ puts "Uploading build from '#{local_path_to_app}' ...".blue
111
+ puts
112
+
113
+ max_percentage = 100
114
+ progress_step = 5
115
+ max_progress = max_percentage / progress_step
116
+
117
+ Net::SCP.upload!(deploy_host, deploy_user, local_path_to_app, deploy_dir, :ssh => { :password => deploy_password }, :recursive => true, :preserve => true) do |_ch, _name, sent, total|
118
+ percentage = (sent.to_f / total.to_f * max_percentage).round(1)
119
+ current_progress = (percentage / progress_step).floor
120
+ progress_bar = '|' + '#' * current_progress + '_' * (max_progress - current_progress) + '|'
121
+ print "#{percentage}% #{progress_bar}\r"
122
+ end
123
+
124
+ puts
125
+ end
126
+
127
+ private_class_method :deploy_internal, :scp
128
+ end
129
+ end
@@ -0,0 +1,179 @@
1
+ module Nixenvironment
2
+ class SCM
3
+ SCM_SVN = 'svn'
4
+ SCM_GIT = 'git'
5
+ SCM_MERCURIAL = 'mercurial'
6
+
7
+ def self.current
8
+ %x[ git status ]
9
+ return SCM_GIT if $?.success?
10
+
11
+ %x[ hg status ]
12
+ return SCM_MERCURIAL if $?.success?
13
+
14
+ %x[ svn info ]
15
+ return SCM_SVN if $?.success?
16
+
17
+ return 'undefined'
18
+ end
19
+
20
+ def self.save_revision
21
+ scm_type = current
22
+ case scm_type
23
+ when SCM_SVN
24
+ puts 'SVN working copy detected'
25
+ current_revision = %x[ svnversion ]
26
+ current_monotonic_revision = current_revision
27
+
28
+ # simply check that the SVN version is a digit
29
+ if Integer(current_revision)
30
+ working_copy_is_clean = 1
31
+ else
32
+ puts "Working copy is not clean because of svnversion returning #{current_revision}"
33
+ working_copy_is_clean = 0
34
+ end
35
+ when SCM_GIT
36
+ puts 'GIT working copy detected'
37
+ current_revision = %x[ git rev-parse HEAD ]
38
+ last_revision_in_repo = %x[ git rev-list --all -n 1 ]
39
+
40
+ # number of pushes before current
41
+ current_monotonic_revision = %x[ git rev-list --all --count ]
42
+
43
+ if current_revision == last_revision_in_repo
44
+ current_monotonic_revision = current_monotonic_revision.succ
45
+ end
46
+
47
+ status = %x[ git status --porcelain ]
48
+
49
+ if status == ''
50
+ working_copy_is_clean = 1
51
+ else
52
+ # TODO: check this out for Unity builds
53
+ # ignore changes to build folder, because it's created by Xcode before the script runs as a build phase
54
+ lines_count = status.lines.count
55
+
56
+ if lines_count != 1
57
+ puts "Working copy is not clean because of status:\n#{status}"
58
+ puts
59
+ working_copy_is_clean = 0
60
+ else
61
+ wc_root = %[ git rev-parse --show-toplevel ]
62
+ modified_path = status[3..-1]
63
+ abs_modified_path = File.join(wc_root, modified_path)
64
+
65
+ # compare path to Xcode env var
66
+ if abs_modified_path == ENV['BUILD_ROOT']
67
+ working_copy_is_clean = 1
68
+ else
69
+ puts "Working copy is not clean because of status:\n#{status}"
70
+ puts
71
+ working_copy_is_clean = 0
72
+ end
73
+ end
74
+ end
75
+ when SCM_MERCURIAL
76
+ puts 'Mercurial working copy detected'
77
+ current_revision = %x[ hg id -i ]
78
+ # number of pushes in current branch
79
+ # TODO: compare with: "$(hg log --template '{rev}:{node|short} {desc|firstline} ({author})\n' | wc -l | tr -d ' ')"
80
+ current_monotonic_revision = %x[ hg id --num --rev tip ]
81
+
82
+ status = %x[ hg status ]
83
+
84
+ if status == ''
85
+ working_copy_is_clean = 1
86
+ else
87
+ # TODO: check this out for Unity builds
88
+ # ignore changes to build folder, because it's created by Xcode before the script runs as a build phase
89
+ lines_count = status.lines.count
90
+
91
+ if lines_count != 1
92
+ puts "Working copy is not clean because of status:\n#{status}"
93
+ puts
94
+ working_copy_is_clean = 0
95
+ else
96
+ wc_root = %[ hg root ]
97
+ modified_path = status[3..-1]
98
+ abs_modified_path = File.join(wc_root, modified_path)
99
+
100
+ # compare path to Xcode env var
101
+ if abs_modified_path == ENV['BUILD_ROOT']
102
+ working_copy_is_clean = 1
103
+ else
104
+ puts "Working copy is not clean because of status:\n#{status}"
105
+ puts
106
+ working_copy_is_clean = 0
107
+ end
108
+ end
109
+ end
110
+ else
111
+ puts 'warning: script must be run from working copy. Revision is undefined.'
112
+ current_revision = 'undefined'
113
+ current_monotonic_revision = 'undefined'
114
+ working_copy_is_clean = 0
115
+ end
116
+
117
+ system("
118
+ echo \"#!/bin/sh\
119
+ #{AUTOGENERATED_FILES_HEADER}
120
+ REVISION='#{current_revision.strip!}'
121
+ MONOTONIC_REVISION='#{current_monotonic_revision.strip!}'
122
+ WORKING_COPY_IS_CLEAN='#{working_copy_is_clean}'\" > #{AUTOGENERATED_LAST_REVISION}
123
+ ")
124
+ end
125
+
126
+ # TODO: implement --all for svn
127
+ def self.clean_working_copy(all)
128
+ puts '--- Clean all specified' if all.present?
129
+ scm_type = current
130
+
131
+ case scm_type
132
+ when SCM_SVN
133
+ puts 'SVN working copy detected, cleaning ...'
134
+
135
+ # revert all changes
136
+ system('svn revert --depth=infinity .')
137
+
138
+ # revert all changes in external directories
139
+ # TODO: rework this for Windows
140
+ system("
141
+ for i in $(svn status | grep ^Performing | cut -d\' -f 2)
142
+ do
143
+ svn revert -R $i
144
+ done")
145
+
146
+ # Wipes out unversioned files from Subversion working copy
147
+ system('svn cleanup . --remove-unversioned')
148
+ when SCM_GIT
149
+ puts 'GIT working copy detected, cleaning ...'
150
+
151
+ # revert all changes
152
+ system('git reset --hard')
153
+
154
+ if all
155
+ # wipe out unversioned files (force, remove whole directories, remove ignored files)
156
+ system('git clean -fdx')
157
+ else
158
+ # wipe out unversioned files (force, remove whole directories)
159
+ system('git clean -fd')
160
+ end
161
+ when SCM_MERCURIAL
162
+ puts 'Mercurial working copy detected, cleaning ...'
163
+
164
+ # revert all changes
165
+ system('hg revert --all')
166
+
167
+ if all
168
+ # wipe out unversioned files
169
+ system('hg --config extensions.purge= clean --all')
170
+ else
171
+ # wipe out unversioned files (except ignored files)
172
+ system('hg --config extensions.purge= clean')
173
+ end
174
+ else
175
+ puts 'Error: script must be run from working copy!'
176
+ end
177
+ end
178
+ end
179
+ end
@@ -1,3 +1,3 @@
1
1
  module Nixenvironment
2
- VERSION = '0.0.70'
2
+ VERSION = '0.0.71'
3
3
  end
@@ -107,6 +107,8 @@ module Nixenvironment
107
107
  build_args << '-showBuildSettings'
108
108
 
109
109
  puts 'Reading config settings ...'
110
+ puts
111
+
110
112
  cmd_output = execute(nil, build_args, true)
111
113
 
112
114
  env_vars_list = cmd_output.split(/\n/).reject(&:empty?)
@@ -167,7 +169,11 @@ module Nixenvironment
167
169
 
168
170
  def self.read_info
169
171
  @info = {}
172
+
173
+ puts
170
174
  cmd_output = execute(nil, %w(-list 2>&1))
175
+ puts
176
+
171
177
  raise "Getting xcode_project_info error! #{cmd_output}" if cmd_output.include?('error')
172
178
 
173
179
  cmd_output = cmd_output.lines[1..-1].join.split(/\n\n/)