xcode_make 0.0.1
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.
- 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: []
|