source: users/pipping/merge.rb @ 26637

Last change on this file since 26637 was 26637, checked in by pipping@…, 10 years ago

test scripts

remove obsolete test from create_testheaders
use a TOP_DIR constant that's identical across all tests

merge.rb

make ORIGINAL_DIR into a constant

  • Property svn:executable set to *
File size: 10.4 KB
Line 
1#!/usr/bin/env ruby -w
2
3# Copyright (c) 2007 Elias Pipping
4#
5# Permission is hereby granted, free of charge, to any person obtaining a copy
6# of this software and associated documentation files (the "Software"), to deal
7# in the Software without restriction, including without limitation the rights
8# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9# copies of the Software, and to permit persons to whom the Software is
10# furnished to do so, subject to the following conditions:
11#
12# The above copyright notice and this permission notice shall be included in
13# all copies or substantial portions of the Software.
14#
15# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21# THE SOFTWARE.
22
23require 'fileutils'
24require 'find'
25require 'optparse'
26require 'set'
27
28###
29# we need GNU File with Apple's patches applied, available here:
30# http://www.opensource.apple.com/darwinsource/Current/file-6.2/patches/
31FILE="/usr/bin/file"
32
33class MergeArguments < Hash
34  def initialize(args)
35    super()
36
37    # set up defaults
38    self[:dry_run] = false
39    self[:force]   = false
40    self[:verbose] = false
41
42    self[:exclude] = Set.new << '.svn' << 'CVS'
43    self[:output]  = File.join(Dir.pwd,'out')
44    self[:root]    = Dir.pwd
45
46    opts = OptionParser.new {|opts|
47      opts.banner = "Usage: #$0 [options] <arch> <arch> [<arch> ...]"
48
49      opts.on(
50        '-r', '--root DIRECTORY',
51        'specify root directory'
52      ) {|root|
53        self[:root] = root || Dir.pwd
54      }
55
56      opts.on(
57        '-o', '--output DIRECTORY',
58        'specify output directory'
59      ) {|out|
60        self[:output] = File.expand_path(out)
61      }
62
63      opts.on(
64        '-e', '--exclude NAMES',
65        'specify names of files/directories to exclude'
66      ) {|exclude|
67        self[:exclude] = Set.new
68        exclude.split.each {|item|
69          self[:exclude] << item
70        }
71      }
72
73      opts.on(
74        '-d', '--dry-run',
75        'perform a dry run'
76      ) {
77        self[:dry_run] = true
78      }
79
80      opts.on(
81        '-f', '--force',
82        'force writing, ignoring collisions'
83      ) {
84        self[:force] = true
85      }
86
87      opts.on(
88        '-v', '--verbose',
89        'enable verbose output'
90      ) {
91        self[:verbose] = true
92      }
93
94      opts.on_tail(
95        '-h', '--help',
96        'display this help and exit'
97      ) {
98        puts opts
99        exit
100      }
101    }
102    opts.parse!(args)
103  end
104end
105
106arguments = MergeArguments.new(ARGV)
107
108# avoid duplicates and trailing slashes
109ARGS=ARGV.collect {|arg| arg.chomp('/')}.uniq
110
111def true_for_all? (path, args, &block)
112  result=true
113  ARGS.each {|arch|
114    result=false unless yield(File.join(args[:root], arch, path), arch)
115  }
116  return result
117end
118
119def lipo (filepath,architectures,args)
120  lipoargs = Array.new
121  architectures.each {|arch|
122    lipoargs << sprintf(
123      '-arch %s %s',
124      arch, File.join(
125        args[:root], arch, filepath
126      )
127    )
128  }
129  lipotarget=File.join(args[:output], filepath)
130  lipocmd = sprintf(
131    'lipo %s -create -o %s',
132    lipoargs.join(' '),
133    lipotarget
134  )
135  if !File.exist?(lipotarget) or args[:force]
136    puts lipocmd if args[:verbose]
137    system lipocmd unless args[:dry_run]
138  end
139end
140
141ORIGINAL_DIR=Dir.pwd
142processed=Set.new
143
144if File.directory?(File.expand_path(arguments[:root]))
145  ARGS.each {|architecture|
146    FileUtils.cd ORIGINAL_DIR, :verbose => false
147    if File.directory? File.join(
148      File.expand_path(arguments[:root]), architecture
149    )
150      FileUtils.cd(
151        File.join(arguments[:root], architecture),
152        :verbose => arguments[:verbose]
153      )
154      Find.find('.') {|path|
155        arguments[:exclude].each {|exclude_me|
156          Find.prune if File.basename(path) == exclude_me
157        }
158        unless processed.include? path
159          my_dir=File.dirname(File.join(arguments[:output], path))
160          # TODO: what if ppc/foo is a dir and i386/foo is a file (symlink)? (1)
161          # TODO: maybe mkdir should only be called *after* we've decided what
162          #       needs to be done with the file
163          unless File.exist? my_dir
164            FileUtils.mkdir_p(
165              my_dir,
166              :verbose => arguments[:verbose],
167              :noop => arguments[:dry_run]
168            )
169          end
170          if true_for_all?(path,arguments) {|filepath,arch|
171            !FileTest.symlink? filepath
172          }
173            if true_for_all?(path, arguments) {|filepath,arch|
174              File.exist? filepath
175            }
176              if true_for_all?(path, arguments) {|filepath,arch|
177                !File.directory? filepath
178              }
179                if true_for_all?(path, arguments) {|filepath,arch|
180                  FileUtils.identical?(
181                    filepath, 
182                    File.join(arguments[:root], ARGS[0], path)
183                  )
184                }
185                  copytarget=File.join(arguments[:output],path)
186                  if !File.exist?(copytarget) or arguments[:force]
187                    FileUtils.cp(
188                      File.join(arguments[:root],ARGS[0],path),
189                      copytarget,
190                      :preserve => !arguments[:force],
191                      :verbose => arguments[:verbose],
192                      :noop => arguments [:dry_run]
193                    )
194                  end
195                else
196                  case File.basename path
197                  # TODO: more cases
198                  when /\.h$/, /\.hpp$/
199                    if !File.exists?(File.join(arguments[:output], path)) or arguments[:force]
200                      open(File.join(arguments[:output], path), 'w') {|file|
201                        ARGS.each {|arch|
202                          file.printf "#ifdef __%s__\n", arch
203                          file.puts open(File.join(arguments[:root],arch,path), 'r').read
204                          file.puts '#endif'
205                        }
206                      }
207                    end
208                  else
209                    file_output = %x{
210                      #{FILE} -b "#{
211                        File.join(arguments[:root],ARGS[0],path)
212                      }"
213                    }.chomp
214                    case file_output
215                    when /^current ar archive/
216                      if true_for_all?(path, arguments) {|filepath,arch|
217                        %x{#{FILE} -b "#{filepath}"}.chomp =~ /^current ar archive/
218                      }
219                        if true_for_all?(path, arguments) {|filepath,arch|
220                          %x{lipo -info "#{filepath}"}.chomp =~ /is architecture: #{arch}$/
221                        }
222                          lipo(path,ARGS,arguments)
223                        else
224                          # ERROR: processing universal binary or wrong arch
225                        end
226                      else
227                        # ERROR: mixed filetypes
228                      end
229                    when /^Mach-O/
230                      if true_for_all?(path, arguments) {|filepath,arch|
231                        %x{#{FILE} -b "#{filepath}"}.chomp =~ /^Mach-O/
232                      }
233                        if true_for_all?(path, arguments) {|filepath,arch|
234                          %x{lipo -info "#{filepath}"}.chomp =~ /is architecture: #{arch}$/
235                        }
236                          links=Hash.new
237                          ARGS.each {|my_arch|
238                            links[my_arch]=%x{
239                              #{
240                                my_arch =~ /64$/ ? 'otool64' : 'otool'
241                              } -arch #{my_arch} -LX #{
242                                File.join(arguments[:root],my_arch,path)
243                              }
244                            }.split("\n").collect {|depline|
245                              depline.lstrip.gsub(
246                                / \(compatibility version \d+(\.\d+)*, current version \d+(\.\d+)*\)/, ''
247                              )
248                            }.reject {|dep|
249                              dep =~ /^\/usr\/lib\//
250                            }.to_set
251                          }
252                          ARGS.each {|my_arch|
253                            unless links[my_arch] == links[ARGS[0]]
254                              missing_in  = links[my_arch]-links[ARGS[0]]
255                              missing_out = links[ARGS[0]]-links[my_arch]
256                              # TODO: come up with a better error message
257                              if missing_in.any? or missing_out.any?
258                                raise sprintf(
259                                  'difference in linking, file %s', path
260                                )
261                              end
262                            end
263                          }
264                          lipo(path,ARGS,arguments)
265                        else
266                          # ERROR: processing universal binary or wrong arch
267                        end
268                      else
269                        # ERROR: mixed filetypes
270                      end
271                    # TODO: handle more filetypes
272                    else
273                      puts "dunno type: #{path}"
274                    end
275                  end
276                end
277              elsif !true_for_all?(path, arguments) {|filepath,arch|
278                File.directory? filepath
279              }
280                # TODO: we have a mix of at least one directory and items that
281                # are not directories
282              end
283            else
284              # TODO: a file is not present in all trees. what's wrong?
285            end
286          elsif true_for_all?(path,arguments) {|filepath,arch|
287            FileTest.symlink? filepath
288          }
289            # TODO: dealing with symlinks
290          else
291            # TODO: dealing with a mix of at least one symlink and items that
292            # are not symlinks
293          end
294          processed << path
295        end
296      }
297    else
298      raise sprintf(
299        'architecture missing or not a directory: %s',
300        architecture
301      )
302    end
303  }
304else
305  raise sprintf(
306    'invalid root directory: %s',
307    arguments[:root]
308  )
309end
310
311FileUtils.cd ORIGINAL_DIR, :verbose => false
Note: See TracBrowser for help on using the repository browser.