fir-cli 1.1.5 → 1.1.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,73 +4,37 @@ module FIR
4
4
  module Build
5
5
 
6
6
  def build_ipa *args, options
7
- # initialize build options
7
+ # check build environment and make build cmd
8
+ check_osx
9
+
8
10
  if args.first.blank? || !File.exist?(args.first)
9
11
  @build_dir = Dir.pwd
10
12
  else
11
13
  @build_dir = File.absolute_path(args.shift.to_s) # pop the first param
12
14
  end
13
15
 
14
- @build_cmd = "xcodebuild build -sdk iphoneos"
15
- @build_tmp_dir = Dir.mktmpdir
16
- @custom_settings = parse_custom_settings(args) # convert ['a=1', 'b=2'] => { 'a' => '1', 'b' => '2' }
17
- @configuration = options[:configuration]
18
- @wrapper_name = File.basename(options[:name].to_s, '.*') + '.app' unless options[:name].blank?
19
- @target_name = options[:target]
20
- @scheme_name = options[:scheme]
21
- @output_path = options[:output].blank? ? "#{@build_dir}/build_ipa" : File.absolute_path(options[:output].to_s)
22
- @dsym_name = @wrapper_name + '.dSYM' unless @wrapper_name.blank?
23
-
24
- # check build environment and make build cmd
25
- check_osx
26
- if options.workspace?
27
- @workspace = check_and_find_workspace(@build_dir)
28
- check_scheme(@scheme_name)
29
- @build_cmd += " -workspace '#{@workspace}' -scheme '#{@scheme_name}'"
30
- else
31
- @project = check_and_find_project(@build_dir)
32
- @build_cmd += " -project '#{@project}'"
33
- end
34
-
35
- @build_cmd += " -configuration '#{@configuration}'" unless @configuration.blank?
36
- @build_cmd += " -target '#{@target_name}'" unless @target_name.blank?
16
+ @token = options[:token] || current_token
17
+ @changelog = options[:changelog].to_s
18
+ @short = options[:short].to_s
19
+ @proj = options[:proj].to_s
37
20
 
38
- # convert { "a" => "1", "b" => "2" } => "a='1' b='2'"
39
- @setting_str = @custom_settings.collect { |k, v| "#{k}='#{v}'" }.join(' ')
40
- @setting_str += " WRAPPER_NAME='#{@wrapper_name}'" unless @wrapper_name.blank?
41
- @setting_str += " TARGET_BUILD_DIR='#{@build_tmp_dir}'" unless @custom_settings['TARGET_BUILD_DIR']
42
- @setting_str += " CONFIGURATION_BUILD_DIR='#{@build_tmp_dir}'" unless @custom_settings['CONFIGURATION_BUILD_DIR']
43
- @setting_str += " DWARF_DSYM_FOLDER_PATH='#{@output_path}'" unless @custom_settings['DWARF_DSYM_FOLDER_PATH']
44
- @setting_str += " DWARF_DSYM_FILE_NAME='#{@dsym_name}'" unless @dsym_name.blank?
21
+ @build_tmp_dir = Dir.mktmpdir
22
+ @output_path = options[:output].blank? ? "#{@build_dir}/fir_build_ipa" : File.absolute_path(options[:output].to_s)
23
+ @ipa_build_cmd = initialize_ipa_build_cmd(args, options)
45
24
 
46
- @build_cmd += " #{@setting_str} 2>&1"
47
- puts @build_cmd if $DEBUG
25
+ puts @ipa_build_cmd if $DEBUG
48
26
 
49
27
  logger.info "Building......"
50
28
  logger_info_dividing_line
51
29
 
52
- logger.info `#{@build_cmd}`
53
-
54
- FileUtils.mkdir_p(@output_path) unless File.exist?(@output_path)
55
- Dir.chdir(@build_tmp_dir) do
56
- apps = Dir["*.app"]
57
- if apps.length == 0
58
- logger.error "Builded has no output app, Can not be packaged"
59
- exit 1
60
- end
30
+ logger.info `#{@ipa_build_cmd}`
61
31
 
62
- apps.each do |app|
63
- ipa_path = File.join(@output_path, "#{File.basename(app, '.app')}.ipa")
64
- zip_app2ipa(File.join(@build_tmp_dir, app), ipa_path)
65
- end
66
- end
32
+ output_ipa
67
33
 
68
- logger.info "Build Success"
34
+ publish_build_ipa if options.publish?
35
+ upload_build_mapping_file if options.mapping?
69
36
 
70
- if options.publish?
71
- ipa_path = Dir["#{@output_path}/*.ipa"].first
72
- publish(ipa_path, short: options[:short], changelog: options[:changelog], token: options[:token])
73
- end
37
+ logger_info_blank_line
74
38
  end
75
39
 
76
40
  def build_apk *args, options
@@ -78,12 +42,89 @@ module FIR
78
42
 
79
43
  private
80
44
 
81
- def parse_custom_settings args
45
+ def initialize_ipa_build_cmd args, options
46
+ ipa_build_cmd = "xcodebuild build -sdk iphoneos"
47
+
48
+ @configuration = options[:configuration]
49
+ @wrapper_name = File.basename(options[:name].to_s, '.*') + '.app' unless options[:name].blank?
50
+ @target_name = options[:target]
51
+ @scheme_name = options[:scheme]
52
+ @dsym_name = @wrapper_name + '.dSYM' unless @wrapper_name.blank?
53
+
54
+ if options.workspace?
55
+ workspace = check_and_find_ios_workspace(@build_dir)
56
+ check_ios_scheme(@scheme_name)
57
+ ipa_build_cmd += " -workspace '#{workspace}' -scheme '#{@scheme_name}'"
58
+ else
59
+ project = check_and_find_ios_project(@build_dir)
60
+ ipa_build_cmd += " -project '#{project}'"
61
+ end
62
+
63
+ ipa_build_cmd += " -configuration '#{@configuration}'" unless @configuration.blank?
64
+ ipa_build_cmd += " -target '#{@target_name}'" unless @target_name.blank?
65
+ ipa_build_cmd += " #{ipa_custom_settings(args)} 2>&1"
66
+
67
+ ipa_build_cmd
68
+ end
69
+
70
+ def ipa_custom_settings args
71
+ custom_settings = parse_ipa_custom_settings(args)
72
+
73
+ # convert { "a" => "1", "b" => "2" } => "a='1' b='2'"
74
+ setting_str = custom_settings.collect { |k, v| "#{k}='#{v}'" }.join(' ')
75
+ setting_str += " WRAPPER_NAME='#{@wrapper_name}'" unless @wrapper_name.blank?
76
+ setting_str += " TARGET_BUILD_DIR='#{@build_tmp_dir}'" unless custom_settings['TARGET_BUILD_DIR']
77
+ setting_str += " CONFIGURATION_BUILD_DIR='#{@build_tmp_dir}'" unless custom_settings['CONFIGURATION_BUILD_DIR']
78
+ setting_str += " DWARF_DSYM_FOLDER_PATH='#{@output_path}'" unless custom_settings['DWARF_DSYM_FOLDER_PATH']
79
+ setting_str += " DWARF_DSYM_FILE_NAME='#{@dsym_name}'" unless @dsym_name.blank?
80
+ setting_str
81
+ end
82
+
83
+ def output_ipa
84
+ FileUtils.mkdir_p(@output_path) unless File.exist?(@output_path)
85
+ Dir.chdir(@build_tmp_dir) do
86
+ apps = Dir["*.app"]
87
+ if apps.length == 0
88
+ logger.error "Builded has no output app, Can not be packaged"
89
+ exit 1
90
+ end
91
+
92
+ apps.each do |app|
93
+ ipa_path = File.join(@output_path, "#{File.basename(app, '.app')}.ipa")
94
+ zip_app2ipa(File.join(@build_tmp_dir, app), ipa_path)
95
+ end
96
+ end
97
+
98
+ logger.info "Build Success"
99
+ end
100
+
101
+ def publish_build_ipa
102
+ logger_info_blank_line
103
+ publish Dir["#{@output_path}/*.ipa"].first, short: @short,
104
+ changelog: @changelog,
105
+ token: @token
106
+ end
107
+
108
+ def upload_build_mapping_file
109
+ logger_info_blank_line
110
+
111
+ @app_info = ipa_info(Dir["#{@output_path}/*.ipa"].first)
112
+ @mapping_file = Dir["#{@output_path}/*.dSYM/Contents/Resources/DWARF/*"].first
113
+
114
+ mapping @mapping_file, proj: @proj,
115
+ build: @app_info[:build],
116
+ version: @app_info[:version],
117
+ token: @token
118
+ end
119
+
120
+ # convert ['a=1', 'b=2'] => { 'a' => '1', 'b' => '2' }
121
+ def parse_ipa_custom_settings args
82
122
  hash = {}
83
123
  args.each do |setting|
84
124
  k, v = setting.split('=', 2).map(&:strip)
85
125
  hash[k] = v
86
126
  end
127
+
87
128
  hash
88
129
  end
89
130
 
@@ -94,13 +135,13 @@ module FIR
94
135
  end
95
136
  end
96
137
 
97
- def check_and_find_project path
138
+ def check_and_find_ios_project path
98
139
  unless File.exist?(path)
99
140
  logger.error "The first param BUILD_DIR must be a xcodeproj directory"
100
141
  exit 1
101
142
  end
102
143
 
103
- if is_project?(path)
144
+ if is_ios_project?(path)
104
145
  project = path
105
146
  else
106
147
  project = Dir["#{path}/*.xcodeproj"].first
@@ -113,13 +154,13 @@ module FIR
113
154
  project
114
155
  end
115
156
 
116
- def check_and_find_workspace path
157
+ def check_and_find_ios_workspace path
117
158
  unless File.exist?(path)
118
159
  logger.error "The first param BUILD_DIR must be a xcworkspace directory"
119
160
  exit 1
120
161
  end
121
162
 
122
- if is_workspace?(path)
163
+ if is_ios_workspace?(path)
123
164
  workspace = path
124
165
  else
125
166
  workspace = Dir["#{path}/*.xcworkspace"].first
@@ -132,18 +173,18 @@ module FIR
132
173
  workspace
133
174
  end
134
175
 
135
- def check_scheme scheme_name
176
+ def check_ios_scheme scheme_name
136
177
  if scheme_name.blank?
137
178
  logger.error "Must provide a scheme by `-S` option when build a workspace"
138
179
  exit 1
139
180
  end
140
181
  end
141
182
 
142
- def is_project? path
183
+ def is_ios_project? path
143
184
  File.extname(path) == '.xcodeproj'
144
185
  end
145
186
 
146
- def is_workspace? path
187
+ def is_ios_workspace? path
147
188
  File.extname(path) == '.xcworkspace'
148
189
  end
149
190
 
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+
3
+ module FIR
4
+ module Config
5
+ CONFIG_PATH = "#{ENV['HOME']}/.fir-cli"
6
+ API_YML_PATH = File.expand_path("../../", __FILE__) + '/api.yml'
7
+ APP_FILE_TYPE = %w(.ipa .apk).freeze
8
+
9
+ def fir_api
10
+ @fir_api ||= YAML.load_file(API_YML_PATH).deep_symbolize_keys[:fir]
11
+ end
12
+
13
+ def bughd_api
14
+ @bughd_api ||= YAML.load_file(API_YML_PATH).deep_symbolize_keys[:bughd]
15
+ end
16
+
17
+ def config
18
+ @config ||= YAML.load_file(CONFIG_PATH).deep_symbolize_keys if File.exist?(CONFIG_PATH)
19
+ end
20
+
21
+ def reload_config
22
+ @config = YAML.load_file(CONFIG_PATH).deep_symbolize_keys
23
+ end
24
+
25
+ def write_config hash
26
+ File.open(CONFIG_PATH, 'w+') { |f| f << YAML.dump(hash) }
27
+ end
28
+
29
+ def current_token
30
+ @token ||= config[:token] if config
31
+ end
32
+
33
+ alias_method :☠, :exit
34
+ end
35
+ end
@@ -0,0 +1,49 @@
1
+ # encoding: utf-8
2
+
3
+ module FIR
4
+ module Http
5
+
6
+ DEFAULT_TIMEOUT = 300
7
+
8
+ def get url, params = {}
9
+ begin
10
+ res = ::RestClient::Request.execute(
11
+ method: :get,
12
+ url: url,
13
+ timeout: DEFAULT_TIMEOUT,
14
+ headers: default_headers.merge(params: params)
15
+ )
16
+ rescue => e
17
+ logger.error e.message.to_s + " - " + e.response.to_s
18
+ exit 1
19
+ end
20
+
21
+ JSON.parse(res.body.force_encoding("UTF-8"), symbolize_names: true)
22
+ end
23
+
24
+ %w(post patch put).each do |method|
25
+ define_method method do |url, query|
26
+ begin
27
+ res = ::RestClient::Request.execute(
28
+ method: method.to_sym,
29
+ url: url,
30
+ payload: query,
31
+ timeout: DEFAULT_TIMEOUT,
32
+ headers: default_headers
33
+ )
34
+ rescue => e
35
+ logger.error e.message.to_s + " - " + e.response.to_s
36
+ exit 1
37
+ end
38
+
39
+ JSON.parse(res.body.force_encoding("UTF-8"), symbolize_names: true)
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def default_headers
46
+ { content_type: :json, source: 'fir-cli', version: FIR::VERSION }
47
+ end
48
+ end
49
+ end
data/lib/fir/util/info.rb CHANGED
@@ -7,6 +7,7 @@ module FIR
7
7
  file_path = File.absolute_path(args.first.to_s)
8
8
  is_all = !options[:all].blank?
9
9
 
10
+ check_file_exist file_path
10
11
  check_supported_file file_path
11
12
 
12
13
  file_type = File.extname(file_path).delete('.')
@@ -16,10 +17,12 @@ module FIR
16
17
 
17
18
  app_info = send("#{file_type}_info", file_path, is_all)
18
19
  app_info.each { |k, v| logger.info "#{k}: #{v}" }
20
+
21
+ logger_info_blank_line
19
22
  end
20
23
 
21
- def ipa_info ipa_path, is_all
22
- ipa = Parser::IPA.new(ipa_path)
24
+ def ipa_info ipa_path, is_all = false
25
+ ipa = FIR::Parser::Ipa.new(ipa_path)
23
26
  app = ipa.app
24
27
 
25
28
  info = {
@@ -52,7 +55,7 @@ module FIR
52
55
  info
53
56
  end
54
57
 
55
- def apk_info apk_path, is_all
58
+ def apk_info apk_path, is_all = false
56
59
  apk = Android::Apk.new(apk_path)
57
60
  info = {
58
61
  type: 'android',
@@ -12,6 +12,7 @@ module FIR
12
12
  write_config(email: user_info.fetch(:email, ''), token: token)
13
13
  reload_config
14
14
  logger.info "Login succeed, current user's email: #{config[:email]}"
15
+ logger_info_blank_line
15
16
  end
16
17
  end
17
18
  end
@@ -0,0 +1,65 @@
1
+ # encoding: utf-8
2
+
3
+ module FIR
4
+ module Mapping
5
+
6
+ def mapping *args, options
7
+ @file_path = File.absolute_path(args.first.to_s)
8
+ @token = options[:token] || current_token
9
+ @proj = options[:proj].to_s
10
+ @version = options[:version].to_s
11
+ @build = options[:build].to_s
12
+
13
+ check_file_exist @file_path
14
+ check_token_cannot_be_blank @token
15
+ check_project_id_cannot_be_blank
16
+
17
+ logger.info "Creating bughd project's version......."
18
+ logger_info_dividing_line
19
+
20
+ @full_version = find_or_create_bughd_full_version
21
+
22
+ logger.info "Uploading mapping file......."
23
+ logger_info_dividing_line
24
+
25
+ upload_mapping_file
26
+
27
+ logger.info "Uploaded succeed: #{bughd_api[:domain]}/project/#{@proj}/settings"
28
+ logger_info_blank_line
29
+ end
30
+
31
+ def find_or_create_bughd_full_version
32
+ url = bughd_api[:project_url] + "/#{@proj}/full_versions"
33
+ post url, version: @version, build: @build, uuid: uuid
34
+ end
35
+
36
+ def upload_mapping_file
37
+ if File.is_dsym?(@file_path)
38
+ tmp_file_path = "#{Dir.tmpdir}/#{File.basename(@file_path)}-fircli.dSYM"
39
+ FileUtils.cp(@file_path, tmp_file_path)
40
+ elsif File.is_txt?(@file_path)
41
+ tmp_file_path = "#{Dir.tmpdir}/#{File.basename(@file_path)}-fircli.txt"
42
+ FileUtils.cp(@file_path, tmp_file_path)
43
+ else
44
+ tmp_file_path = @file_path
45
+ end
46
+
47
+ url = bughd_api[:full_version_url] + "/#{@full_version[:id]}"
48
+ patch url, file: File.new(tmp_file_path, 'rb'), project_id: @proj, uuid: uuid
49
+ end
50
+
51
+ private
52
+
53
+ def check_project_id_cannot_be_blank
54
+ if @proj.blank?
55
+ logger.error "Project id can't be blank"
56
+ exit 1
57
+ end
58
+ end
59
+
60
+ def uuid
61
+ @uuid ||= fetch_user_uuid(@token)
62
+ end
63
+
64
+ end
65
+ end
data/lib/fir/util/me.rb CHANGED
@@ -6,13 +6,14 @@ module FIR
6
6
  def me
7
7
  check_logined
8
8
 
9
- if user_info = fetch_user_info(current_token)
10
- email = user_info.fetch(:email, '')
11
- name = user_info.fetch(:name, '')
9
+ user_info = fetch_user_info(current_token)
12
10
 
13
- logger.info "Login succeed, current user's email: #{email}"
14
- logger.info "Login succeed, current user's name: #{name}"
15
- end
11
+ email = user_info.fetch(:email, '')
12
+ name = user_info.fetch(:name, '')
13
+
14
+ logger.info "Login succeed, current user's email: #{email}"
15
+ logger.info "Login succeed, current user's name: #{name}"
16
+ logger_info_blank_line
16
17
  end
17
18
  end
18
19
  end
@@ -0,0 +1,157 @@
1
+ # encoding: utf-8
2
+
3
+ module FIR
4
+ module Parser
5
+
6
+ class Ipa
7
+
8
+ def initialize(path)
9
+ @path = path
10
+ end
11
+
12
+ def app
13
+ @app ||= App.new(app_path)
14
+ end
15
+
16
+ def app_path
17
+ @app_path ||= Dir.glob(File.join(contents, 'Payload', '*.app')).first
18
+ end
19
+
20
+ def cleanup
21
+ return unless @contents
22
+ FileUtils.rm_rf(@contents)
23
+ @contents = nil
24
+ end
25
+
26
+ def metadata
27
+ return unless has_metadata?
28
+ @metadata ||= CFPropertyList.native_types(CFPropertyList::List.new(file: metadata_path).value)
29
+ end
30
+
31
+ def has_metadata?
32
+ File.file? metadata_path
33
+ end
34
+
35
+ def metadata_path
36
+ @metadata_path ||= File.join(@contents, 'iTunesMetadata.plist')
37
+ end
38
+
39
+ def release_type
40
+ has_metadata? ? 'store' : 'adhoc'
41
+ end
42
+
43
+ def contents
44
+ return if @contents
45
+ @contents = "fir-cli_tmp/ipa_files-#{Time.now.to_i}"
46
+
47
+ Zip::File.open(@path) do |zip_file|
48
+ zip_file.each do |f|
49
+ f_path = File.join(@contents, f.name)
50
+ FileUtils.mkdir_p(File.dirname(f_path))
51
+ zip_file.extract(f, f_path) unless File.exist?(f_path)
52
+ end
53
+ end
54
+
55
+ @contents
56
+ end
57
+
58
+ class App
59
+
60
+ def initialize(path)
61
+ @path = path
62
+ end
63
+
64
+ def info
65
+ @info ||= CFPropertyList.native_types(
66
+ CFPropertyList::List.new(file: File.join(@path, 'Info.plist')).value)
67
+ end
68
+
69
+ def name
70
+ info['CFBundleName']
71
+ end
72
+
73
+ def identifier
74
+ info['CFBundleIdentifier']
75
+ end
76
+
77
+ def display_name
78
+ info['CFBundleDisplayName']
79
+ end
80
+
81
+ def version
82
+ info['CFBundleVersion']
83
+ end
84
+
85
+ def short_version
86
+ info['CFBundleShortVersionString']
87
+ end
88
+
89
+ def icons
90
+ @icons ||= begin
91
+ icons = []
92
+ info['CFBundleIcons']['CFBundlePrimaryIcon']['CFBundleIconFiles'].each do |name|
93
+ icons << get_image(name)
94
+ icons << get_image("#{name}@2x")
95
+ end
96
+ icons.delete_if { |i| !i }
97
+ rescue NoMethodError
98
+ []
99
+ end
100
+ end
101
+
102
+ def mobileprovision
103
+ return unless has_mobileprovision?
104
+ return @mobileprovision if @mobileprovision
105
+
106
+ cmd = "security cms -D -i \"#{mobileprovision_path}\""
107
+ begin
108
+ @mobileprovision = CFPropertyList.native_types(CFPropertyList::List.new(data: `#{cmd}`).value)
109
+ rescue CFFormatError
110
+ @mobileprovision = {}
111
+ end
112
+ end
113
+
114
+ def has_mobileprovision?
115
+ File.file? mobileprovision_path
116
+ end
117
+
118
+ def mobileprovision_path
119
+ @mobileprovision_path ||= File.join(@path, 'embedded.mobileprovision')
120
+ end
121
+
122
+ def hide_developer_certificates
123
+ mobileprovision.delete('DeveloperCertificates') if has_mobileprovision?
124
+ end
125
+
126
+ def devices
127
+ mobileprovision['ProvisionedDevices'] if has_mobileprovision?
128
+ end
129
+
130
+ def distribution_name
131
+ "#{mobileprovision['Name']} - #{mobileprovision['TeamName']}" if has_mobileprovision?
132
+ end
133
+
134
+ def release_type
135
+ if has_mobileprovision?
136
+ if devices
137
+ 'adhoc'
138
+ else
139
+ 'inhouse'
140
+ end
141
+ end
142
+ end
143
+
144
+ private
145
+
146
+ def get_image name
147
+ path = File.join(@path, "#{name}.png")
148
+ return nil unless File.exist?(path)
149
+ path
150
+ end
151
+ end
152
+ end
153
+
154
+ class Apk
155
+ end
156
+ end
157
+ end