#!/usr/bin/env ruby # Copyright (C) 2020 Igalia S. L. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF # THE POSSIBILITY OF SUCH DAMAGE. # Wrapper for a .cpp -> .o compilation command. It # 1. converts the command to generate a `.s' file # 2. runs the ASM postprocessor on it to generate the final `.s` file # 3. assembles the `.s` file to a `.o` file $asm_suffix_pre = ".pre.s" $asm_suffix = ".s" $postprocessor = "#{File.dirname($0)}/resolve-asm-file-conflicts.rb" $intermediate_paths = [] # We need to work with indices a lot and unfortunately 'getoptlong' in # the standard library doesn't expose optind, so we're going with # array searches for simplicity. def index_nofail(ary, f, errmsg) idx = ary.index { |el| f.call(el) } if idx.nil? $stderr.puts(errmsg) exit(3) end idx end # Find and return the source file for this compilation command, # removing it from the args. Note that the path (A) as it appears here # (coming from a cmake rule) is likely to be different to the # anonymous argument (B) to the compilation command. However, A will # be a suffix of B. # # Exit with an error if the argument is not there. This has already # been checked by `cxx-wrapper`, otherwise we wouldn't be running. def extract_input!(args) prefix = '-DPOSTPROCESS_ASM=' idx = index_nofail(args, Proc.new { |arg| arg.start_with?(prefix) }, "No `-DPOSTPROCESS_ASM` argument`") path = args[idx][prefix.size..-1] if path.size == 0 $stderr.puts("Empty path in -DPOSTPROCESS_ASM=") exit(3) end # We only need this to be defined for the preprocessor (not any # wrapper) from now on. args[idx] = "-DPOSTPROCESS_ASM" return path end # Get the index of the first argument ending in this suffix. # # Exit with an error if the argument isn't there. We're only ever # called with arguments we know are being passed in by the build # system. def get_arg_idx_suffix(args, wanted) index_nofail(args, Proc.new { |arg| arg.end_with?(wanted) }, "No argument ends with #{wanted}") end # Get index of a given argument. Die if it's not there. def get_arg_idx(args, wanted) index_nofail(args, Proc.new { |arg| arg == wanted }, "No `#{wanted}` argument") end # Get the index of `-o` and verify that an argument follows. # Both are guaranteed to exist (from our build system). def get_o_idx(args) i = get_arg_idx(args, '-o') if (i + 1) >= args.size $stderr.puts("No argument to `-o`") exit(3) end i end # Run command and die if it fails, propagating the exit code. def run_cmd(cmd) pid = Process.spawn(*cmd) Process.waitpid(pid) ret = $? if not ret.success? $stderr.puts("Error running cmd: #{ret}") exit(ret.exitstatus) end end # Convert # cxx -o blah.o -c blah.cpp # to # cxx -o blah.s -S blah.cpp def build_cxx_cmd(args) c_idx = get_arg_idx(args, '-c') o_idx = get_o_idx(args) cxx_args = args.clone cxx_args[c_idx] = '-S' o_path = cxx_args[o_idx + 1] cxx_args[o_idx + 1] = o_path.sub(/[.]o$/, $asm_suffix) $intermediate_paths << cxx_args[o_idx + 1] if cxx_args[o_idx + 1] == o_path $stderr.puts("Output file name not an object file: `#{o_path}`") exit(3) end cxx_args end # Do # mv blah.S blah.pre.S # The reason we do a rename instead of directly generating the .pre.s # file when compiling is so that the corresponding .dwo file will have # the correct name embedded. def rename_s_file(args) o_path = args[get_o_idx(args) + 1] File.rename(o_path.sub(/[.]o$/, $asm_suffix), o_path.sub(/[.]o$/, $asm_suffix_pre)) end # Build # postprocessor blah.pre.S blah.S def build_postprocessor_cmd(args) o_path = args[get_o_idx(args) + 1] pp_args = [ $postprocessor, o_path.sub(/[.]o$/, $asm_suffix_pre), # input o_path.sub(/[.]o$/, $asm_suffix) # output ] $intermediate_paths << pp_args[-2] $intermediate_paths << pp_args[-1] pp_args end # Build # cxx -o blah.o -c blah.S def build_as_cmd(args, i_path) i_idx = get_arg_idx_suffix(args, i_path) o_path = args[get_o_idx(args) + 1] as_args = args.clone i_path = as_args[i_idx] as_args[i_idx] = o_path.sub(/[.]o$/, $asm_suffix) as_args end args = ARGV.to_a i_path = extract_input!(args) begin run_cmd(build_cxx_cmd(args)) rename_s_file(args) run_cmd(build_postprocessor_cmd(args)) run_cmd(build_as_cmd(args, i_path)) ensure $intermediate_paths.each { |p| if File.exist?(p) File.delete(p) end } end