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 +191 -0
- data/ext/xcode_make/extconf.rb +15 -0
- data/ext/xcode_make/wrapper.cpp +81 -0
- metadata +60 -0
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: []
|