| #!/bin/bash |
| # |
| # clang-wrapper <compile-command...> |
| # |
| # When WK_UNIFIED_SOURCES_BUNDLE_POLICY is "Bundle", or when compiling |
| # a non-unified-source file, this script is a simple pass-through. |
| # |
| # Otherwise, this script makes N copies of <compile-command...>, one for each |
| # unified source bundle member, and uses ninja + clang + libtool to compile |
| # each one and then merge the results into a unified .o and .d file. |
| # |
| # The end result is as-if we built the unified bundle, but with the performance |
| # of building indivdiual member files with individual dependency tracking. |
| # |
| # This works because ninja's only contract is: input => tool => (output, deps). |
| # From the outer ninja's perspective, it doesn't matter how 'tool' computes |
| # (output, deps). |
| # |
| # Bundling is purely an optimization decision. Either policy is always valid. |
| # |
| # The pass-through path is bash to minimize startup cost. The NoBundle path |
| # re-execs this same file under perl -x, which skips ahead to the perl |
| # shebang below. |
| |
| if [[ "$WK_UNIFIED_SOURCES_BUNDLE_POLICY" != NoBundle ]]; then |
| exec "$@" |
| fi |
| |
| exec /usr/bin/perl -x "$0" "$@" |
| |
| #!/usr/bin/perl |
| use strict; |
| use warnings; |
| |
| # Extract the flags that describe our input and output, and preserve the rest |
| # for reuse in our ninja commands. |
| my $sourceFile; |
| my $outputFile; |
| my $depFile; |
| my @driverArgs; |
| for (my $i = 0; $i < scalar(@ARGV); ++$i) { |
| my $arg = $ARGV[$i]; |
| if ($arg eq '-c') { |
| $sourceFile = $ARGV[++$i]; |
| next; |
| } |
| if ($arg eq '-o') { |
| $outputFile = $ARGV[++$i]; |
| next; |
| } |
| if ($arg eq '-MF') { |
| $depFile = $ARGV[++$i]; |
| next; |
| } |
| if ($arg eq '-MT' || $arg eq '-x') { |
| ++$i; |
| next; |
| } |
| push(@driverArgs, $arg); |
| } |
| |
| # Pass through non-unified sources. |
| if ($sourceFile !~ m{/unified-sources/UnifiedSource}) { |
| exec(@ARGV); |
| } |
| |
| my ($extension) = $sourceFile =~ /\.(\w+)$/; |
| my $membersFolder = "$outputFile.members"; |
| mkdir($membersFolder); |
| |
| # Expand the bundle into one stub source file per member. |
| my @memberObjects; |
| my $ccEdges = ''; |
| my $memberCount = 0; |
| open(my $bundle, '<', $sourceFile) or die("$sourceFile: $!\n"); |
| while (my $line = <$bundle>) { |
| chomp($line); |
| if ($line !~ /^#include /) { |
| next; |
| } |
| my $stub = "$membersFolder/m$memberCount.$extension"; |
| my $object = "$membersFolder/m$memberCount.o"; |
| writeFileIfChanged($stub, "$line\n"); |
| $ccEdges .= "build $object | $object.d: cc $stub\n"; |
| push(@memberObjects, $object); |
| ++$memberCount; |
| } |
| close($bundle); |
| |
| my $ccPrefix = join(' ', map { shellQuote($_) } @driverArgs); |
| my $quotedDepFile = shellQuote($depFile); |
| my $memberObjectList = join(' ', @memberObjects); |
| my $memberDepList = join(' ', map { "$_.d" } @memberObjects); |
| |
| # Rule bodies are literal: $cc_prefix, $in, $out, etc. are all ninja variables. |
| my $rules = <<'END_OF_RULES'; |
| rule cc |
| command = $cc_prefix -MD -MT $out -MF $out.d -o $out -c $in |
| depfile = $out.d |
| description = member $in |
| |
| rule merge |
| command = $cc_prefix -Qunused-arguments -r -nostdlib -Wl,-keep_private_externs -o $out $in && cat $member_deps > $bundle_dep |
| description = merge $out |
| END_OF_RULES |
| |
| my $buildNinja = <<"END_OF_NINJA"; |
| ninja_required_version = 1.3 |
| builddir = $membersFolder |
| cc_prefix = $ccPrefix |
| bundle_dep = $quotedDepFile |
| member_deps = $memberDepList |
| $rules |
| $ccEdges |
| build $outputFile | $depFile: merge $memberObjectList |
| default $outputFile |
| END_OF_NINJA |
| |
| writeFileIfChanged("$membersFolder/build.ninja", $buildNinja); |
| |
| chomp(my $ninja = `which ninja 2>/dev/null` || `xcrun -f ninja`); |
| exec($ninja, '--quiet', '-f', "$membersFolder/build.ninja"); |
| die("exec ninja: $!\n"); |
| |
| sub shellQuote { |
| my ($s) = @_; |
| if ($s =~ /^[A-Za-z0-9_\-+=:,.\/]+$/) { |
| return $s; |
| } |
| $s =~ s/'/'\\''/g; |
| return "'$s'"; |
| } |
| |
| sub writeFileIfChanged { |
| my ($path, $newContent) = @_; |
| if (-f $path) { |
| local $/; |
| open(my $in, '<', $path); |
| my $oldContent = <$in>; |
| close($in); |
| if (defined($oldContent) && $oldContent eq $newContent) { |
| return; |
| } |
| } |
| open(my $out, '>', $path) or die("$path: $!\n"); |
| print($out $newContent); |
| close($out); |
| } |