xcode_make 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/bin/xcode-make ADDED
@@ -0,0 +1,191 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #require 'xcode_make'
4
+ #XcodeMake.run_make_wrapper(*ARGV)
5
+
6
+ MAKE_BINARY = '/usr/bin/make'
7
+
8
+ project_file_path = ENV['PROJECT_FILE_PATH']
9
+ if project_file_path.nil? || project_file_path.empty?
10
+ # Seems we're not being run from within Xcode. No point in doing
11
+ # the wrapping and parsing if we have no project file to write to.
12
+ exec(MAKE_BINARY, *ARGV)
13
+ end
14
+
15
+ Signal.trap("SIGINT") do
16
+ exit(128 + 2)
17
+ end
18
+
19
+ require 'Open3'
20
+ MARKER = '!!!xmake!!!'
21
+ TMP_FILE = 'xcode-make.tmp'
22
+
23
+ # Make sure our wrappers are called, so we can print the PWD
24
+ ENV['PATH'] = "#{File.dirname(__FILE__)}/wrappers:#{ENV['PATH']}"
25
+
26
+ compile_step = Regexp.new("^#{MARKER} PWD=(\\S+?)\\s.*\\s-c(\\s|$)")
27
+ ignored_compiler_parts = Regexp.new('(^| )*-(o|x|include|arch|Xclang|name) +[^ ]+ *')
28
+ source_file = Regexp.new('(^| )([^- ][^ ]*\.(c|cpp|m|mm))( |$)')
29
+ defines_and_includes = Regexp.new('\s(-D\S+|-I\S+)')
30
+
31
+ make_directory_switch = Regexp.new('^make.*?: Entering directory `(.*?)\'$')
32
+ issue = Regexp.new('^(.+?):(\d+:){0,2} (warning|error|info): (.*)$')
33
+
34
+ discovered_files = {}
35
+
36
+ ENV['PROJECT_TEMP_DIR'] = "/tmp"
37
+ tmp_file_path = File.join(ENV['PROJECT_TEMP_DIR'], TMP_FILE)
38
+ if File.exists?(tmp_file_path)
39
+ File.open(tmp_file_path, "rb") {|f| discovered_files = Marshal.load(f)}
40
+ end
41
+
42
+ ARGV.unshift(MAKE_BINARY)
43
+
44
+ # Try to catch errors from makefiles by having make print the
45
+ # working directory for each recursive makefile.
46
+ ARGV.push('--print-directory')
47
+ make_directory = ''
48
+
49
+ # Sadly we can't use the same working directory for compilation issues,
50
+ # as make might have recursed to another directory by the time clang
51
+ # finishes compilation and prints issues to stderr. This should not be
52
+ # an issue with make itself, assuming it flushes stderr each time it
53
+ # recurses into a new directory.
54
+
55
+ build_exit_status = 0
56
+
57
+ Open3.popen2e(*ARGV) do |stdin, stdout_and_stderr, wait_thr|
58
+ stdout_and_stderr.each do |line|
59
+
60
+ if line.start_with?(MARKER)
61
+ compiler_line = compile_step.match(line)
62
+ next if not compiler_line
63
+
64
+ pwd = compiler_line[1]
65
+
66
+ file_name = line.gsub(ignored_compiler_parts, ' ')
67
+ file_name = source_file.match(file_name)
68
+ next if not file_name
69
+
70
+ # The input filename should already be absolute, thanks to
71
+ # the work done in the wrapper.
72
+ file_name = file_name[0].strip
73
+
74
+ compiler_flags = ""
75
+ line.scan(defines_and_includes) do |define_or_include|
76
+ define_or_include = define_or_include[0]
77
+ define_or_include.match(/-I(.+)/) do |include|
78
+ # We still need to make sure include paths are absolute, as the
79
+ # wrapper does not handle that part.
80
+ define_or_include = "-I#{File.absolute_path(include[1], pwd)}"
81
+ end
82
+
83
+ compiler_flags += " #{define_or_include}"
84
+ end
85
+
86
+ discovered_files[file_name] = compiler_flags.strip
87
+
88
+ next # Early return, we don't want to print these lines
89
+
90
+ elsif make_directory_switch.match(line)
91
+ make_directory = $1
92
+
93
+ elsif issue.match(line)
94
+ issue_file = $1
95
+
96
+ # This will only have an effect on issues reported by make, as clang
97
+ # will report absolute file names thanks to the work done in the wrapper.
98
+ line.gsub!(issue_file, File.absolute_path(issue_file, make_directory))
99
+ end
100
+
101
+ # Make sure the output reaches Xcode
102
+ puts line
103
+ $stdout.flush
104
+ end
105
+
106
+ build_exit_status = wait_thr.value.exitstatus
107
+ end
108
+
109
+ if build_exit_status > 0
110
+ # Save any discovered files for the next possibly successful build
111
+ File.open(tmp_file_path, "wb") do |file|
112
+ Marshal.dump(discovered_files, file)
113
+ end
114
+
115
+ exit(build_exit_status)
116
+ end
117
+
118
+ # We're now ready to update the Xcode project file
119
+
120
+ puts "xcode-make: Build completed successfully. Processing discovered files..."
121
+ $stdout.flush
122
+
123
+ old_verbose = $VERBOSE
124
+ $VERBOSE = nil # Ignore warnings from xcoder
125
+ require 'xcoder'
126
+ $VERBOSE = old_verbose
127
+
128
+ # FIXME: Read encoding from project file instead of assuming UTF-8
129
+ Encoding.default_external = 'utf-8'
130
+ project = Xcode.project(project_file_path)
131
+
132
+ indexer_target = project.targets.find{|t| t.name == 'Indexer' }
133
+ if not indexer_target
134
+ # Create the indexer target
135
+ indexer_target = project.create_target('Indexer', :native)
136
+ project.global_configs.each do |config|
137
+ indexer_target.create_configuration(config.name) do |new_config|
138
+ # For some reason we need PRODUCT_NAME. Inherit the rest from the global config
139
+ new_config.build_settings = { 'PRODUCT_NAME' => '$(TARGET_NAME)' }
140
+ end
141
+ end
142
+
143
+ # TODO: Remove added scheme for Indexer (requires support from xcoder)
144
+ # TODO: Add Indexer as Analyze step to the main build target's scheme
145
+
146
+ project.save!
147
+ end
148
+
149
+ sources_build_phase = indexer_target.sources_build_phase || indexer_target.create_build_phase(:sources)
150
+
151
+ existing_sources = Hash[ *sources_build_phase.files.collect { |file| [ file.file_ref.path, file.settings['COMPILER_FLAGS'] ] }.flatten ]
152
+
153
+ changed_sources = discovered_files.dup.delete_if { |k, v| existing_sources[k] == v }
154
+
155
+ puts "xcode-make: Existing: #{existing_sources.length}, discovered: #{discovered_files.length}, changed: #{changed_sources.length}"
156
+
157
+ changed_sources.each do |file_name, compiler_flags|
158
+
159
+ file = sources_build_phase.file(file_name)
160
+ if not file
161
+ basename = File.basename(file_name)
162
+ file_reference = Xcode::FileReference.file({ "path" => file_name, 'name' => basename, 'sourceTree' => '<absolute>'})
163
+ file_reference = project.registry.add_object(file_reference)
164
+
165
+ sources_build_phase.add_build_file(file_reference, { 'COMPILER_FLAGS' => compiler_flags })
166
+ puts "xcode-make: Added #{file_name} to indexer"
167
+
168
+ else
169
+ file.settings['COMPILER_FLAGS'] = compiler_flags
170
+ file.save!
171
+ puts "xcode-make: Updated compiler flags for #{file_name}"
172
+ end
173
+
174
+ $stdout.flush
175
+ end
176
+
177
+ if changed_sources.length > 0
178
+ puts "xcode-make: Saving project file..."
179
+ # Saving the project is not atomic, as xcoder does a second step of reformatting,
180
+ # after the initial save. We do a manual save to make it appear atomic for Xcode.
181
+ tmp_project_path = '/tmp/xmake'
182
+ project.save(tmp_project_path)
183
+ FileUtils.copy(File.join(tmp_project_path, 'project.pbxproj'), project_file_path)
184
+ end
185
+
186
+ # No need for the temp file anymore
187
+ File.delete(tmp_file_path) if File.exists?(tmp_file_path)
188
+
189
+ puts "xcode-make: Done!"
190
+
191
+ exit(build_exit_status)
@@ -0,0 +1,15 @@
1
+ # Trick mkmf into linking a binary instead of a shared library
2
+ require 'rbconfig'
3
+ RbConfig::MAKEFILE_CONFIG['LINK_SO'] = "$(CXX) -o $@ $(OBJS) $(LDFLAGS)"
4
+
5
+ require 'mkmf'
6
+ CONFIG['DLEXT'] = "bin"
7
+ create_makefile('../bin/wrappers/wrapper') do |config|
8
+ config = configuration('') << <<RULES
9
+ all:
10
+ install: symlink
11
+ symlink: install-so
12
+ ln -s $(RUBYARCHDIR)/$(DLLIB) $(RUBYARCHDIR)/clang
13
+ ln -s $(RUBYARCHDIR)/$(DLLIB) $(RUBYARCHDIR)/clang++
14
+ RULES
15
+ end
@@ -0,0 +1,81 @@
1
+ #include <dirent.h>
2
+ #include <libgen.h>
3
+ #include <stdio.h>
4
+ #include <stdlib.h>
5
+ #include <string.h>
6
+ #include <string>
7
+ #include <unistd.h>
8
+
9
+ #include <mach-o/dyld.h>
10
+
11
+ using namespace std;
12
+
13
+ void replace_all(std::string& str, const std::string& from, const std::string& to)
14
+ {
15
+ if (from.empty())
16
+ return;
17
+
18
+ size_t start_pos = 0;
19
+ while ((start_pos = str.find(from, start_pos)) != std::string::npos) {
20
+ str.replace(start_pos, from.length(), to);
21
+ start_pos += to.length();
22
+ }
23
+ }
24
+
25
+ int main(int argc, char** argv)
26
+ {
27
+ char* program_copy = strdup(argv[0]);
28
+ char* program_name = basename(program_copy);
29
+ free(program_copy);
30
+
31
+ if (strcmp(program_name, "wrapper.bin") == 0) {
32
+ printf("usage: symlink %s to an application of your choice\n", program_name);
33
+ return 64;
34
+ }
35
+
36
+ char binary_path[2048];
37
+ uint32_t size = sizeof(binary_path);
38
+ if (_NSGetExecutablePath(binary_path, &size) != 0) {
39
+ fprintf(stderr, "failed to get executable path!");
40
+ return 1;
41
+ }
42
+
43
+ char* binary_dirname = dirname(binary_path);
44
+
45
+ char* absolute_path = realpath(binary_dirname, NULL);
46
+ string absolute_path_string(absolute_path);
47
+ free(absolute_path);
48
+
49
+ string path(getenv("PATH"));
50
+ absolute_path_string.append(":");
51
+ replace_all(path, absolute_path_string, "");
52
+ setenv("PATH", path.c_str(), 1);
53
+
54
+ char* working_path = realpath(".", NULL);
55
+ printf("!!!xmake!!! PWD=%s %s", working_path, program_name);
56
+ free(working_path);
57
+
58
+ for (int i = 1; i < argc; i++) {
59
+ // FIXME: We might need better detection of what we concider the
60
+ // input file than just looking at the extension.
61
+ if (strcmp(argv[i] + strlen(argv[i]) - 4, ".cpp") == 0
62
+ || strcmp(argv[i] + strlen(argv[i]) - 2, ".c") == 0
63
+ || strcmp(argv[i] + strlen(argv[i]) - 3, ".mm") == 0
64
+ || strcmp(argv[i] + strlen(argv[i]) - 2, ".m") == 0) {
65
+
66
+ // By making the input filename absolute, we trick clang into
67
+ // outputting errors and warnings with an absolute path as well,
68
+ // which lets Xcode correctly pinpoint the file in question.
69
+ argv[i] = realpath(argv[i], NULL);
70
+ }
71
+
72
+ printf(" %s", argv[i]);
73
+ }
74
+ printf("\n");
75
+
76
+ fflush(stdout);
77
+ execvp(program_name, argv);
78
+
79
+ fprintf(stderr, "xmake: command not found: %s\n", program_name);
80
+ return 127;
81
+ }
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: xcode_make
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Tor Arne Vestbø
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-09-04 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: xcoder
16
+ requirement: &70203812621260 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 0.1.12
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70203812621260
25
+ description: A wrapper for using Xcode's indexer with external targets
26
+ email: torarnv@gmail.com
27
+ executables:
28
+ - xcode-make
29
+ extensions:
30
+ - ext/xcode_make/extconf.rb
31
+ extra_rdoc_files: []
32
+ files:
33
+ - ext/xcode_make/wrapper.cpp
34
+ - ext/xcode_make/extconf.rb
35
+ - bin/xcode-make
36
+ homepage: https://github.com/torarnv/xcode-make
37
+ licenses: []
38
+ post_install_message:
39
+ rdoc_options: []
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: 1.9.2
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ requirements: []
55
+ rubyforge_project:
56
+ rubygems_version: 1.8.10
57
+ signing_key:
58
+ specification_version: 3
59
+ summary: Xcode-make!
60
+ test_files: []