generate-bindings-all.pl   [plain text]


#!/usr/bin/env perl
#
# Copyright (C) 2016 Sony Interactive Entertainment Inc.
#
# 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.
#

use strict;
use warnings;
use FindBin;
use lib $FindBin::Bin;

use File::Basename;
use File::Spec;
use File::Find;
use Getopt::Long;

my $perl = $^X;
my $scriptDir = $FindBin::Bin;
my @idlDirectories;
my $outputDirectory;
my $idlFilesList;
my $ppIDLFilesList;
my $generator;
my @generatorDependency;
my $defines;
my $preprocessor;
my $supplementalDependencyFile;
my @ppExtraOutput;
my @ppExtraArgs;
my $numOfJobs = 1;
my $idlAttributesFile;
my $showProgress;

GetOptions('include=s@' => \@idlDirectories,
           'outputDir=s' => \$outputDirectory,
           'idlFilesList=s' => \$idlFilesList,
           'ppIDLFilesList=s' => \$ppIDLFilesList,
           'generator=s' => \$generator,
           'generatorDependency=s@' => \@generatorDependency,
           'defines=s' => \$defines,
           'preprocessor=s' => \$preprocessor,
           'supplementalDependencyFile=s' => \$supplementalDependencyFile,
           'ppExtraOutput=s@' => \@ppExtraOutput,
           'ppExtraArgs=s@' => \@ppExtraArgs,
           'idlAttributesFile=s' => \$idlAttributesFile,
           'numOfJobs=i' => \$numOfJobs,
           'showProgress' => \$showProgress);

$| = 1;
my @idlFiles;
open(my $fh, '<', $idlFilesList) or die "Cannot open $idlFilesList";
@idlFiles = map { CygwinPathIfNeeded(s/\r?\n?$//r) } <$fh>;
close($fh) or die;

my @ppIDLFiles;
open($fh, '<', $ppIDLFilesList) or die "Cannot open $ppIDLFilesList";
@ppIDLFiles = map { CygwinPathIfNeeded(s/\r?\n?$//r) } <$fh>;
close($fh) or die;

my %oldSupplements;
my %newSupplements;
if ($supplementalDependencyFile) {
    my @output = ($supplementalDependencyFile, @ppExtraOutput);
    my @deps = ($ppIDLFilesList, @ppIDLFiles, @generatorDependency);
    if (needsUpdate(\@output, \@deps)) {
        readSupplementalDependencyFile($supplementalDependencyFile, \%oldSupplements) if -e $supplementalDependencyFile;
        my @args = (File::Spec->catfile($scriptDir, 'preprocess-idls.pl'),
                    '--defines', $defines,
                    '--idlFilesList', $ppIDLFilesList,
                    '--supplementalDependencyFile', $supplementalDependencyFile,
                    @ppExtraArgs);
        printProgress("Preprocess IDL");
        executeCommand($perl, @args) == 0 or die;
    }
    readSupplementalDependencyFile($supplementalDependencyFile, \%newSupplements);
}

my @args = (File::Spec->catfile($scriptDir, 'generate-bindings.pl'),
            '--defines', $defines,
            '--generator', $generator,
            '--outputDir', $outputDirectory,
            '--preprocessor', $preprocessor,
            '--idlAttributesFile', $idlAttributesFile,
            '--write-dependencies');
push @args, map { ('--include', $_) } @idlDirectories;
push @args, '--supplementalDependencyFile', $supplementalDependencyFile if $supplementalDependencyFile;

my %directoryCache;
buildDirectoryCache();

my @idlFilesToUpdate = grep &{sub {
    if (defined($oldSupplements{$_})
        && @{$oldSupplements{$_}} ne @{$newSupplements{$_} or []}) {
        # Re-process the IDL file if its supplemental dependencies were added or removed
        return 1;
    }
    my ($filename, $dirs, $suffix) = fileparse($_, '.idl');
    my $sourceFile = File::Spec->catfile($outputDirectory, "JS$filename.cpp");
    my $headerFile = File::Spec->catfile($outputDirectory, "JS$filename.h");
    my $depFile = File::Spec->catfile($outputDirectory, "JS$filename.dep");
    my @output = ($sourceFile, $headerFile);
    my @deps = ($_,
                $idlAttributesFile,
                @generatorDependency,
                @{$newSupplements{$_} or []},
                implicitDependencies($depFile));
    needsUpdate(\@output, \@deps);
}}, @idlFiles;

my $abort = 0;
my $totalCount = @idlFilesToUpdate;
my $currentCount = 0;

spawnGenerateBindingsIfNeeded() for (1 .. $numOfJobs);
while (waitpid(-1, 0) != -1) {
    if ($?) {
        $abort = 1;
    }
    spawnGenerateBindingsIfNeeded();
}
exit $abort;

sub needsUpdate
{
    my ($objects, $depends) = @_;
    my $oldestObjectTime;
    for (@$objects) {
        return 1 if !-f;
        my $m = mtime($_);
        if (!defined $oldestObjectTime || $m < $oldestObjectTime) {
            $oldestObjectTime = $m;
        }
    }
    for (@$depends) {
        die "Missing required dependency: $_" if !-f;
        my $m = mtime($_);
        if ($oldestObjectTime < $m) {
            return 1;
        }
    }
    return 0;
}

sub mtime
{
    my ($file) = @_;
    return (stat $file)[9];
}

sub spawnGenerateBindingsIfNeeded
{
    return if $abort;
    return unless @idlFilesToUpdate;
    my $batchCount = 30;
    # my $batchCount = int(($totalCount - $currentCount) / $numOfJobs) || 1;
    my @files = splice(@idlFilesToUpdate, 0, $batchCount);
    for (@files) {
        $currentCount++;
        my $basename = basename($_);
        printProgress("[$currentCount/$totalCount] $basename");
    }
    my $pid = spawnCommand($perl, @args, @files);
    $abort = 1 unless defined $pid;
}

sub buildDirectoryCache
{
    my $wanted = sub {
        $directoryCache{$_} = $File::Find::name;
        $File::Find::prune = 1 unless ~/\./;
    };
    find($wanted, @idlDirectories);
}

sub implicitDependencies
{
    my ($depFile) = @_;
    return () unless -f $depFile;
    open(my $fh, '<', $depFile) or die "Cannot open $depFile";
    my $firstLine = <$fh>;
    close($fh) or die;
    my (undef, $deps) = split(/ : /, $firstLine);
    my @deps = split(/\s+/, $deps);
    return map { $directoryCache{$_} or () } @deps;
}

sub executeCommand
{
    if ($^O eq 'MSWin32') {
        return system(quoteCommand(@_));
    }
    return system(@_);
}

sub spawnCommand
{
    my $pid = fork();
    if ($pid == 0) {
        @_ = quoteCommand(@_) if ($^O eq 'MSWin32');
        exec(@_);
        die "Cannot exec";
    }
    return $pid;
}

sub quoteCommand
{
    return map {
        '"' . s/([\\\"])/\\$1/gr . '"';
    } @_;
}

sub CygwinPathIfNeeded
{
    my $path = shift;
    return Cygwin::win_to_posix_path($path) if ($^O eq 'cygwin');
    return $path;
}

sub readSupplementalDependencyFile
{
    my $filename = shift;
    my $supplements = shift;
    open(my $fh, '<', $filename) or die "Cannot open $filename";
    while (<$fh>) {
        my ($idlFile, @followingIdlFiles) = split(/\s+/);
        $supplements->{$idlFile} = [sort @followingIdlFiles];
    }
    close($fh) or die;
}

sub printProgress
{
    return unless $showProgress;
    my $msg = shift;
    print "$msg\n";
}