1#!/usr/bin/perl -w
2
3# Copyright (C) 2007, 2008, 2009, 2010 Apple Inc.  All rights reserved.
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions
7# are met:
8#
9# 1.  Redistributions of source code must retain the above copyright
10#     notice, this list of conditions and the following disclaimer. 
11# 2.  Redistributions in binary form must reproduce the above copyright
12#     notice, this list of conditions and the following disclaimer in the
13#     documentation and/or other materials provided with the distribution. 
14# 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15#     its contributors may be used to endorse or promote products derived
16#     from this software without specific prior written permission. 
17#
18# THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21# DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29# Script to sort "children" and "files" sections in Xcode project.pbxproj files
30
31use strict;
32
33use File::Basename;
34use File::Spec;
35use File::Temp qw(tempfile);
36use Getopt::Long;
37
38sub sortChildrenByFileName($$);
39sub sortFilesByFileName($$);
40
41# Files (or products) without extensions
42my %isFile = map { $_ => 1 } qw(
43    create_hash_table
44    jsc
45    minidom
46    testapi
47    testjsglue
48);
49
50my $printWarnings = 1;
51my $showHelp;
52
53my $getOptionsResult = GetOptions(
54    'h|help'         => \$showHelp,
55    'w|warnings!'    => \$printWarnings,
56);
57
58if (scalar(@ARGV) == 0 && !$showHelp) {
59    print STDERR "ERROR: No Xcode project files (project.pbxproj) listed on command-line.\n";
60    undef $getOptionsResult;
61}
62
63if (!$getOptionsResult || $showHelp) {
64    print STDERR <<__END__;
65Usage: @{[ basename($0) ]} [options] path/to/project.pbxproj [path/to/project.pbxproj ...]
66  -h|--help           show this help message
67  -w|--[no-]warnings  show or suppress warnings (default: show warnings)
68__END__
69    exit 1;
70}
71
72for my $projectFile (@ARGV) {
73    if (basename($projectFile) =~ /\.xcodeproj$/) {
74        $projectFile = File::Spec->catfile($projectFile, "project.pbxproj");
75    }
76
77    if (basename($projectFile) ne "project.pbxproj") {
78        print STDERR "WARNING: Not an Xcode project file: $projectFile\n" if $printWarnings;
79        next;
80    }
81
82    # Grab the mainGroup for the project file
83    my $mainGroup = "";
84    open(IN, "< $projectFile") || die "Could not open $projectFile: $!";
85    while (my $line = <IN>) {
86        $mainGroup = $2 if $line =~ m#^(\s*)mainGroup = ([0-9A-F]{24} /\* .+ \*/);$#;
87    }
88    close(IN);
89
90    my ($OUT, $tempFileName) = tempfile(
91        basename($projectFile) . "-XXXXXXXX",
92        DIR => dirname($projectFile),
93        UNLINK => 0,
94    );
95
96    # Clean up temp file in case of die()
97    $SIG{__DIE__} = sub {
98        close(IN);
99        close($OUT);
100        unlink($tempFileName);
101    };
102
103    my @lastTwo = ();
104    open(IN, "< $projectFile") || die "Could not open $projectFile: $!";
105    while (my $line = <IN>) {
106        if ($line =~ /^(\s*)files = \(\s*$/) {
107            print $OUT $line;
108            my $endMarker = $1 . ");";
109            my @files;
110            while (my $fileLine = <IN>) {
111                if ($fileLine =~ /^\Q$endMarker\E\s*$/) {
112                    $endMarker = $fileLine;
113                    last;
114                }
115                push @files, $fileLine;
116            }
117            print $OUT sort sortFilesByFileName @files;
118            print $OUT $endMarker;
119        } elsif ($line =~ /^(\s*)children = \(\s*$/) {
120            print $OUT $line;
121            my $endMarker = $1 . ");";
122            my @children;
123            while (my $childLine = <IN>) {
124                if ($childLine =~ /^\Q$endMarker\E\s*$/) {
125                    $endMarker = $childLine;
126                    last;
127                }
128                push @children, $childLine;
129            }
130            if ($lastTwo[0] =~ m#^\s+\Q$mainGroup\E = \{$#) {
131                # Don't sort mainGroup
132                print $OUT @children;
133            } else {
134                print $OUT sort sortChildrenByFileName @children;
135            }
136            print $OUT $endMarker;
137        } else {
138            print $OUT $line;
139        }
140
141        push @lastTwo, $line;
142        shift @lastTwo if scalar(@lastTwo) > 2;
143    }
144    close(IN);
145    close($OUT);
146
147    unlink($projectFile) || die "Could not delete $projectFile: $!";
148    rename($tempFileName, $projectFile) || die "Could not rename $tempFileName to $projectFile: $!";
149}
150
151exit 0;
152
153sub sortChildrenByFileName($$)
154{
155    my ($a, $b) = @_;
156    my $aFileName = $1 if $a =~ /^\s*[A-Z0-9]{24} \/\* (.+) \*\/,$/;
157    my $bFileName = $1 if $b =~ /^\s*[A-Z0-9]{24} \/\* (.+) \*\/,$/;
158    my $aSuffix = $1 if $aFileName =~ m/\.([^.]+)$/;
159    my $bSuffix = $1 if $bFileName =~ m/\.([^.]+)$/;
160    if ((!$aSuffix && !$isFile{$aFileName} && $bSuffix) || ($aSuffix && !$bSuffix && !$isFile{$bFileName})) {
161        return !$aSuffix ? -1 : 1;
162    }
163    return lc($aFileName) cmp lc($bFileName);
164}
165
166sub sortFilesByFileName($$)
167{
168    my ($a, $b) = @_;
169    my $aFileName = $1 if $a =~ /^\s*[A-Z0-9]{24} \/\* (.+) in /;
170    my $bFileName = $1 if $b =~ /^\s*[A-Z0-9]{24} \/\* (.+) in /;
171    return lc($aFileName) cmp lc($bFileName);
172}
173