cocoapods-util 0.0.11

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.
Files changed (35) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/Gemfile +13 -0
  4. data/LICENSE +21 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +54 -0
  7. data/Rakefile +13 -0
  8. data/cocoapods-util.gemspec +25 -0
  9. data/lib/cocoapods-util/CocoapodsUtilHook.rb +18 -0
  10. data/lib/cocoapods-util/cocoapods-extend/extend.rb +2 -0
  11. data/lib/cocoapods-util/cocoapods-extend/install/list.rb +140 -0
  12. data/lib/cocoapods-util/cocoapods-extend/install.rb +15 -0
  13. data/lib/cocoapods-util/cocoapods-extend/repo/push.rb +40 -0
  14. data/lib/cocoapods-util/cocoapods-extend/repo/push_helper.rb +36 -0
  15. data/lib/cocoapods-util/cocoapods-extend/repo.rb +15 -0
  16. data/lib/cocoapods-util/command/util.rb +28 -0
  17. data/lib/cocoapods-util/command.rb +1 -0
  18. data/lib/cocoapods-util/gem_version.rb +3 -0
  19. data/lib/cocoapods-util/hooks/installer.rb +4 -0
  20. data/lib/cocoapods-util/libsource/source.rb +81 -0
  21. data/lib/cocoapods-util/libsource/source_linker.rb +184 -0
  22. data/lib/cocoapods-util/package/helper/builder.rb +194 -0
  23. data/lib/cocoapods-util/package/helper/framework.rb +61 -0
  24. data/lib/cocoapods-util/package/helper/framework_builder.rb +147 -0
  25. data/lib/cocoapods-util/package/helper/library_builder.rb +47 -0
  26. data/lib/cocoapods-util/package/helper/pod_utils.rb +126 -0
  27. data/lib/cocoapods-util/package/package.rb +177 -0
  28. data/lib/cocoapods-util/user_interface/build_failed_report.rb +15 -0
  29. data/lib/cocoapods-util/xcframework/xcframework.rb +66 -0
  30. data/lib/cocoapods-util/xcframework/xcramework_build.rb +133 -0
  31. data/lib/cocoapods-util.rb +1 -0
  32. data/lib/cocoapods_plugin.rb +3 -0
  33. data/spec/command/util_spec.rb +12 -0
  34. data/spec/spec_helper.rb +50 -0
  35. metadata +126 -0
@@ -0,0 +1,126 @@
1
+ module Pod
2
+ class Command
3
+ class Util < Command
4
+ class Package < Util
5
+ private
6
+
7
+ def build_static_sandbox
8
+ static_sandbox_root = Pathname.new(config.sandbox_root)
9
+ Sandbox.new(static_sandbox_root)
10
+ end
11
+
12
+ def install_pod(platform_name, sandbox)
13
+ podfile = podfile_from_spec(
14
+ @path,
15
+ @spec.name,
16
+ platform_name,
17
+ @spec.deployment_target(platform_name),
18
+ @subspecs,
19
+ @spec_sources,
20
+ @use_modular_headers,
21
+ @dependency_config
22
+ )
23
+
24
+ static_installer = Installer.new(sandbox, podfile)
25
+ static_installer.install!
26
+
27
+ unless static_installer.nil?
28
+ # default build settings
29
+ default_build_settings = Hash.new
30
+ default_build_settings["CLANG_MODULES_AUTOLINK"] = "NO"
31
+ default_build_settings["GCC_GENERATE_DEBUGGING_SYMBOLS"] = "YES" # 生成Debug编译信息
32
+ default_build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64" unless @xcframework # 非xcframework排除ios simulator 64位架构
33
+ default_build_settings["EXCLUDED_ARCHS[sdk=appletvsimulator*]"] = "arm64" unless @xcframework # 非xcframework排除tvos simulator 64位架构
34
+ default_build_settings["BUILD_LIBRARY_FOR_DISTRIBUTION"] = "YES" # 编译swift生成swiftinterface文件
35
+
36
+ # merge user setting
37
+ default_build_settings.merge!(@build_settings) unless @build_settings.empty?
38
+
39
+ static_installer.pods_project.targets.each do |target|
40
+ target.build_configurations.each do |config|
41
+ default_build_settings.each { |key, value| config.build_settings[key.to_s] = value.to_s }
42
+ end
43
+ end
44
+ static_installer.pods_project.save
45
+ end
46
+
47
+ static_installer
48
+ end
49
+
50
+ def podfile_from_spec(path, spec_name, platform_name, deployment_target, subspecs, sources, use_modular_headers, dependency_config)
51
+ options = {}
52
+ if path
53
+ if @local
54
+ options[:path] = path
55
+ else
56
+ options[:podspec] = path
57
+ end
58
+ end
59
+ options[:subspecs] = subspecs if subspecs
60
+ Pod::Podfile.new do
61
+ sources.each { |s| source s }
62
+ platform(platform_name, deployment_target)
63
+ pod(spec_name, options) unless dependency_config.keys.include?(spec_name)
64
+
65
+ dependency_config.each do |name, config|
66
+ dependency_options = {}
67
+ config.each do |key, value|
68
+ dependency_options[key.to_sym] = value
69
+ end
70
+ pod(name, dependency_options)
71
+ end
72
+
73
+ use_modular_headers! if use_modular_headers
74
+ install!('cocoapods',
75
+ :integrate_targets => false,
76
+ :deterministic_uuids => false)
77
+
78
+ target('packager') do
79
+ inherit! :complete
80
+ end
81
+ end
82
+ end
83
+
84
+ def binary_only?(spec)
85
+ deps = spec.dependencies.map { |dep| spec_with_name(dep.name) }
86
+ [spec, *deps].each do |specification|
87
+ %w(vendored_frameworks vendored_libraries).each do |attrib|
88
+ if specification.attributes_hash[attrib]
89
+ return true
90
+ end
91
+ end
92
+ end
93
+
94
+ false
95
+ end
96
+
97
+ def spec_with_name(name)
98
+ return if name.nil?
99
+
100
+ set = Pod::Config.instance.sources_manager.search(Dependency.new(name))
101
+ return nil if set.nil?
102
+
103
+ set.specification.root
104
+ end
105
+
106
+ def spec_with_path(path)
107
+ return if path.nil? || !Pathname.new(path).exist?
108
+
109
+ @path = Pathname.new(path).expand_path
110
+
111
+ if @path.directory?
112
+ help! @path + ': is a directory.'
113
+ return
114
+ end
115
+
116
+ unless ['.podspec', '.json'].include? @path.extname
117
+ help! @path + ': is not a podspec.'
118
+ return
119
+ end
120
+
121
+ Specification.from_file(@path)
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,177 @@
1
+ require 'cocoapods-util/package/helper/pod_utils'
2
+ require 'cocoapods-util/package/helper/builder'
3
+ require 'cocoapods-util/package/helper/framework'
4
+
5
+ require 'tmpdir'
6
+ require 'json'
7
+
8
+ module Pod
9
+ class Command
10
+ class Util < Command
11
+ class Package < Util
12
+ self.summary = '通过podspec编译静态库(library/framework/xcframework)'
13
+ self.description = "本插件属于在cocoapods-packager的基础上做的修改,支持编译swift。另外支持use_modular_headers!、排除模拟器、排除特定架构、编译xcframework等。"
14
+ self.arguments = [
15
+ CLAide::Argument.new('NAME', true),
16
+ CLAide::Argument.new('SOURCE', false)
17
+ ]
18
+
19
+ def self.options
20
+ [
21
+ ['--force', 'Overwrite existing files.'],
22
+ ['--library', 'Generate static library.'],
23
+ ['--framework', 'Generate static framework.'],
24
+ ['--xcframework', 'Generate static xcframework.'],
25
+ ['--local', 'Use local state rather than published versions.'],
26
+ ['--configuration', 'Build the specified configuration (e.g. Debug). Defaults to Release'],
27
+ ['--subspecs', 'Only include the given subspecs'],
28
+ ['--spec-sources=private,https://github.com/CocoaPods/Specs.git', 'The sources to pull dependent ' \
29
+ 'pods from (defaults to https://github.com/CocoaPods/Specs.git)'],
30
+ ['--exclude-sim', '排除模拟器架构,仅编译真机对应的架构。'],
31
+ ['--use-modular-headers', '开启use_modular_headers!'],
32
+ ['--dependency-config={}', '依赖的pod文件配置,为一个json dictionary,可以配置branch、tag、source源等。配置方式:{"PodA":{"git":"xxx","branch":"xxx"},"PodB":{"source":"xxx"}}'],
33
+ ['--contains-resources', '生成的framework中是否包含bundle文件,默认不把bundle文件放到framework中。'],
34
+ ['--platforms=ios,osx,watchos,tvos,all', '选择编译的平台架构,默认`all`,编译全部平台。'],
35
+ ['--build-settings={}', 'xcode build_settings。{"EXCLUDED_ARCHS[sdk=iphonesimulator*]":"arm64"}']
36
+ ]
37
+ end
38
+
39
+ def initialize(argv)
40
+ @framework = argv.flag?('framework')
41
+ @xcframework = argv.flag?('xcframework')
42
+ @library = argv.flag?('library')
43
+ @local = argv.flag?('local', false)
44
+ @package_type = if @xcframework
45
+ :static_xcframework
46
+ elsif @framework
47
+ :static_framework
48
+ elsif @library
49
+ :static_library
50
+ else
51
+ :static_library
52
+ end
53
+ @force = argv.flag?('force')
54
+ @exclude_sim = argv.flag?('exclude-sim', false)
55
+ @use_modular_headers = argv.flag?('use-modular-headers', false)
56
+ @name = argv.shift_argument
57
+ @source = argv.shift_argument
58
+ @spec_sources = argv.option('spec-sources', 'https://github.com/CocoaPods/Specs.git').split(',')
59
+
60
+ subspecs = argv.option('subspecs')
61
+ @subspecs = subspecs.split(',') unless subspecs.nil?
62
+
63
+ @framework_contains_resources = argv.flag?('contains-resources', false)
64
+ @platforms = argv.option('platforms', 'all')
65
+ @build_settings = JSON.parse(argv.option('build-settings', '{}'))
66
+
67
+ dependency_config = argv.option('dependency-config', '{}')
68
+ @dependency_config = JSON.parse(dependency_config)
69
+
70
+ @config = argv.option('configuration', 'Release')
71
+
72
+ @source_dir = Dir.pwd
73
+ @is_spec_from_path = false
74
+ @spec = spec_with_path(@name)
75
+ @is_spec_from_path = true if @spec
76
+ @spec ||= spec_with_name(@name)
77
+ super
78
+ end
79
+
80
+ def validate!
81
+ super
82
+ help! 'A podspec name or path is required.' unless @spec
83
+ help! '--local option can only be used when a local `.podspec` path is given.' if @local && !@is_spec_from_path
84
+ help! '编译静态库.a不允许设置包含resources' if !(@framework || @xcframework) && @framework_contains_resources
85
+ end
86
+
87
+ def run
88
+ if @spec.nil?
89
+ help! 'Unable to find a podspec with path or name.'
90
+ return
91
+ end
92
+
93
+ target_dir, work_dir = create_working_directory
94
+ return if target_dir.nil?
95
+ build_package
96
+
97
+ `mv "#{work_dir}" "#{target_dir}"`
98
+ Dir.chdir(@source_dir)
99
+ end
100
+
101
+ private
102
+
103
+ def build_in_sandbox(platform)
104
+ config.installation_root = Pathname.new(Dir.pwd)
105
+ config.sandbox_root = 'Pods'
106
+
107
+ static_sandbox = build_static_sandbox()
108
+ static_installer = install_pod(platform.name, static_sandbox)
109
+
110
+ begin
111
+ perform_build(platform, static_sandbox, static_installer)
112
+ ensure # in case the build fails; see Builder#xcodebuild.
113
+ Pathname.new(config.sandbox_root).rmtree
114
+ FileUtils.rm_f('Podfile.lock')
115
+ # clean build
116
+ FileUtils.rm_rf('build')
117
+ end
118
+ end
119
+
120
+ def build_package
121
+ platforms = @platforms.split(',')
122
+ all_platforms = platforms.include?('all')
123
+ @spec.available_platforms.each do |platform|
124
+ if all_platforms || platforms.include?(platform.name.to_s)
125
+ build_in_sandbox(platform)
126
+ end
127
+ end
128
+ end
129
+
130
+ def create_target_directory
131
+ target_dir = "#{@source_dir}/#{@spec.name}-#{@spec.version}"
132
+ if File.exist? target_dir
133
+ if @force
134
+ Pathname.new(target_dir).rmtree
135
+ else
136
+ UI.puts "Target directory '#{target_dir}' already exists."
137
+ return nil
138
+ end
139
+ end
140
+ target_dir
141
+ end
142
+
143
+ def create_working_directory
144
+ target_dir = create_target_directory
145
+ return if target_dir.nil?
146
+
147
+ work_dir = Dir.tmpdir + '/cocoapods-' + Array.new(8) { rand(36).to_s(36) }.join
148
+ UI.puts "work_dir: #{work_dir}" if @verbose
149
+ Pathname.new(work_dir).mkdir
150
+ Dir.chdir(work_dir)
151
+
152
+ [target_dir, work_dir]
153
+ end
154
+
155
+ def perform_build(platform, static_sandbox, static_installer)
156
+ static_sandbox_root = config.sandbox_root.to_s
157
+
158
+ builder = Pod::Builder.new(
159
+ platform,
160
+ static_installer,
161
+ @source_dir,
162
+ static_sandbox_root,
163
+ static_sandbox.public_headers.root,
164
+ @spec,
165
+ @config,
166
+ @exclude_sim,
167
+ @framework_contains_resources,
168
+ @verbose
169
+ )
170
+
171
+ builder.build(@package_type)
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
177
+
@@ -0,0 +1,15 @@
1
+ module Pod
2
+ module UserInterface
3
+ module BuildFailedReport
4
+ class << self
5
+ def report(command, output)
6
+ <<-EOF
7
+ Build command failed: #{command}
8
+ Output:
9
+ #{output.map { |line| " #{line}" }.join}
10
+ EOF
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,66 @@
1
+ require 'cocoapods-util/xcframework/xcramework_build.rb'
2
+
3
+ module Pod
4
+ class Command
5
+ class Util < Command
6
+ class XCFramework < Util
7
+ self.summary = '根据传入framework在同目录下生成xcframework'
8
+ self.description = <<-DESC
9
+ 根据传入framework在同目录下生成xcframework
10
+ DESC
11
+ self.command = 'xcframework'
12
+ self.arguments = [
13
+ CLAide::Argument.new('FRAMEWORK_PATH', true)
14
+ ]
15
+
16
+ def self.options
17
+ [
18
+ ['--force', '覆盖已经存在的文件'],
19
+ ['--create-swiftinterface', '有编译swift文件的framework,如果不包含swiftinterface则无法生成xcframework。
20
+ 设置该参数会生成一个swiftinterface文件解决create失败的问题。']
21
+ ]
22
+ end
23
+
24
+ def initialize(argv)
25
+ @file_path = argv.shift_argument
26
+ @force = argv.flag?('force')
27
+ @create_swiftinterface = argv.flag?('create-swiftinterface')
28
+ super
29
+ end
30
+
31
+ def validate!
32
+ super
33
+ help! '必须传入framework路径或名称.' unless @file_path
34
+ end
35
+
36
+ def run
37
+ # 获取真实路径,~ 为进程所有者的主目录
38
+ @file_path = File.expand_path(@file_path)
39
+ if !File.exist?(@file_path) || !(@file_path =~ /\.framework$/)
40
+ help! "路径不存在或传入的路径不是framework文件"
41
+ return
42
+ end
43
+
44
+ source_dir = File.dirname(@file_path)
45
+ framework_name = File.basename(@file_path, ".framework")
46
+
47
+ target_dir = "#{source_dir}/#{framework_name}.xcframework"
48
+ if File.exist?(target_dir)
49
+ if @force
50
+ Pathname.new(target_dir).rmtree
51
+ else
52
+ help! "#{target_dir}已经存在,使用`--force`可以覆盖已有文件"
53
+ end
54
+ end
55
+
56
+ builder = XCFrameworkBuilder.new(
57
+ framework_name,
58
+ source_dir,
59
+ @create_swiftinterface
60
+ )
61
+ builder.build_static_xcframework
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,133 @@
1
+ module Pod
2
+ class XCFrameworkBuilder
3
+ def initialize(name, source_dir, create_swiftinterface)
4
+ @name = name
5
+ @source_dir = source_dir
6
+ @create_swiftinterface = create_swiftinterface
7
+ end
8
+
9
+ def build_static_xcframework
10
+ framework_path = "#{@source_dir}/#{@name}.framework"
11
+ # 可执行文件名称
12
+ lib_file = "#{@name}"
13
+ # 可执行文件完整路径
14
+ lib_path = "#{framework_path}/#{lib_file}"
15
+ # 可执行文件不存在,退出
16
+ unless File.exist? lib_path
17
+ UI.puts("没有找到可执行文件,请检查输入的framework")
18
+ return nil
19
+ end
20
+ # 如果可执行文件为软链接类型,获取realpath
21
+ if File.ftype(lib_path) == 'link'
22
+ lib_file = File.readlink(lib_path)
23
+ lib_path = "#{framework_path}/#{lib_file}"
24
+ end
25
+
26
+ framework_paths = Array.new
27
+ # 获取可执行文件的支持架构
28
+ archs = `lipo -archs #{lib_path}`.split
29
+ os_archs = sim_archs = []
30
+ if archs.empty?
31
+ UI.puts "framework文件中没有检查到任何编译架构,请使用`lipo -info`或`lipo -archs`检查文件支持的架构。"
32
+ return
33
+ elsif archs.count == 1
34
+ framework_paths += [framework_path]
35
+ else
36
+ platform = `strings #{lib_path} | grep -E -i '/Platforms/.*\.platform/' | head -n 1`.chomp!
37
+ if platform =~ /iPhone[^\.]*\.platform/ # iphoneos iphonesimulator
38
+ os_archs = archs & ['arm64', 'armv7', 'armv7s']
39
+ sim_archs = archs & ['i386', 'x86_64']
40
+ elsif platform =~ /MacOSX.platform/ # macosx
41
+ os_archs = ['arm64', 'x86_64']
42
+ elsif platform =~ /Watch[^\.]*\.platform/ # watchos watchsimulator
43
+ os_archs = archs & ['armv7k', 'arm64_32']
44
+ sim_archs = archs & ['arm64', 'i386', 'x86_64']
45
+ elsif platform =~ /AppleTV[^\.]*\.platform/ # appletvos appletvsimulator
46
+ os_archs = archs & ['arm64']
47
+ sim_archs = archs & ['x86_64'] # 'arm64' 'x86_64'
48
+ else
49
+ os_archs = archs & ['arm64', 'armv7', 'armv7s']
50
+ sim_archs = archs & ['i386', 'x86_64']
51
+ end
52
+ end
53
+
54
+ # 1. remove os/simulator paths
55
+ clean_intermediate_path
56
+
57
+ # 2. copy os framework
58
+ if os_archs.count > 0
59
+ path = Pathname.new("#{@source_dir}/#{os_target_path}")
60
+ path.mkdir unless path.exist?
61
+ `cp -a #{framework_path} #{path}/`
62
+
63
+ fwk_path = "#{path}/#{@name}.framework"
64
+ framework_paths += ["#{fwk_path}"]
65
+ `lipo -extract #{os_archs.join(' -extract ')} "#{fwk_path}/#{lib_file}" -output "#{fwk_path}/#{lib_file}"`
66
+ end
67
+ # 3. copy simulation framework
68
+ if sim_archs.count > 0
69
+ path = Pathname.new("#{@source_dir}/#{simulator_target_path}")
70
+ path.mkdir unless path.exist?
71
+ `cp -a #{framework_path} #{path}/`
72
+
73
+ fwk_path = "#{path}/#{@name}.framework"
74
+ framework_paths += ["#{fwk_path}"]
75
+ `lipo -extract #{sim_archs.join(' -extract ')} "#{fwk_path}/#{lib_file}" -output "#{fwk_path}/#{lib_file}"`
76
+ end
77
+
78
+ # 4. generate xcframework
79
+ begin
80
+ generate_xcframework(framework_paths)
81
+ ensure # in case the create-xcframework fails; remove the temp directory.
82
+ clean_intermediate_path
83
+ end
84
+ end
85
+
86
+ def generate_xcframework(framework_paths)
87
+ UI.puts("Generate #{@name}.xcframework")
88
+
89
+ framework_paths.each { |framework_path|
90
+ # check_swiftmodule
91
+ swiftmodule_path = Dir.glob("#{framework_path}/Modules/*.swiftmodule").first
92
+ unless swiftmodule_path.nil?
93
+ if Dir.glob("#{swiftmodule_path}/*.swiftinterface").empty?
94
+ unless @create_swiftinterface
95
+ UI.puts "Framework中包含swiftmodule文件,但是没有swiftinterface,无法创建xcframework,请检查Framework文件。或者使用`--create-swiftinterface`参数"
96
+ return
97
+ end
98
+ arm_swiftinterface = Pathname.new("#{swiftmodule_path}/arm.swiftinterface")
99
+ File.new(arm_swiftinterface, "w+").close
100
+ end
101
+ end
102
+ }
103
+
104
+ # build xcframework
105
+ command = "xcodebuild -create-xcframework -framework #{framework_paths.join(' -framework ')} -output #{@source_dir}/#{@name}.xcframework 2>&1"
106
+ output = `#{command}`.lines.to_a
107
+ result = $?
108
+ # show error
109
+ if result.exitstatus != 0
110
+ puts UI::BuildFailedReport.report(command, output)
111
+ Process.exit
112
+ end
113
+ UI.puts("Generate #{@name}.xcframework succees")
114
+ end
115
+
116
+ private
117
+ def clean_intermediate_path
118
+ [os_target_path, simulator_target_path].each do |path|
119
+ file_path = "#{@source_dir}/#{path}"
120
+ if File.exist? file_path
121
+ FileUtils.rm_rf(file_path)
122
+ end
123
+ end
124
+ end
125
+
126
+ def os_target_path
127
+ "#{@name}_temp_os"
128
+ end
129
+ def simulator_target_path
130
+ "#{@name}_temp_simulator"
131
+ end
132
+ end
133
+ end
@@ -0,0 +1 @@
1
+ require 'cocoapods-util/gem_version'
@@ -0,0 +1,3 @@
1
+ require 'cocoapods-util/command'
2
+ require 'cocoapods-util/user_interface/build_failed_report.rb'
3
+ require 'cocoapods-util/CocoapodsUtilHook.rb'
@@ -0,0 +1,12 @@
1
+ require File.expand_path('../../spec_helper', __FILE__)
2
+
3
+ module Pod
4
+ describe Command::Util do
5
+ describe 'CLAide' do
6
+ it 'registers it self' do
7
+ Command.parse(%w{ util }).should.be.instance_of Command::Util
8
+ end
9
+ end
10
+ end
11
+ end
12
+
@@ -0,0 +1,50 @@
1
+ require 'pathname'
2
+ ROOT = Pathname.new(File.expand_path('../../', __FILE__))
3
+ $:.unshift((ROOT + 'lib').to_s)
4
+ $:.unshift((ROOT + 'spec').to_s)
5
+
6
+ require 'bundler/setup'
7
+ require 'bacon'
8
+ require 'mocha-on-bacon'
9
+ require 'pretty_bacon'
10
+ require 'pathname'
11
+ require 'cocoapods'
12
+
13
+ Mocha::Configuration.prevent(:stubbing_non_existent_method)
14
+
15
+ require 'cocoapods_plugin'
16
+
17
+ #-----------------------------------------------------------------------------#
18
+
19
+ module Pod
20
+
21
+ # Disable the wrapping so the output is deterministic in the tests.
22
+ #
23
+ UI.disable_wrap = true
24
+
25
+ # Redirects the messages to an internal store.
26
+ #
27
+ module UI
28
+ @output = ''
29
+ @warnings = ''
30
+
31
+ class << self
32
+ attr_accessor :output
33
+ attr_accessor :warnings
34
+
35
+ def puts(message = '')
36
+ @output << "#{message}\n"
37
+ end
38
+
39
+ def warn(message = '', actions = [])
40
+ @warnings << "#{message}\n"
41
+ end
42
+
43
+ def print(message)
44
+ @output << message
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ #-----------------------------------------------------------------------------#