#!/usr/bin/perl # # Copyright 2011 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # # A simple little build system. # # See the configuration at the bottom of this file. use strict; use Getopt::Long; use FindBin; my $opt_list; my $opt_eachclean; my $opt_verbose; my $opt_test; my $opt_deps; chdir($FindBin::Bin) or die "Couldn't chdir to $FindBin::Bin: $!"; GetOptions("list" => \$opt_list, "eachclean" => \$opt_eachclean, "verbose" => \$opt_verbose, "test" => \$opt_test, "deps" => \$opt_deps, ) or usage(); sub usage { die < bool (was it already built?) my %targets; # dir -> { deps => [ ... ], } read_targets(); if ($opt_list) { print "Available targets:\n"; foreach my $target (sort keys %targets) { print " * $target\n"; } exit; } if ($opt_deps) { foreach my $target (sort keys %targets) { find_go_camli_deps($target); my $t = $targets{$target} or die; print "$target: @{ $t->{deps} }\n"; } exit; } if ($opt_eachclean) { my $target = shift; die "--eachclean doesn't take a target\n" if $target; foreach my $target (sort keys %targets) { clean(); %built = (); build($target); } exit; } my $target = shift or usage(); if ($target eq "clean") { clean(); exit; } if ($target eq "allfast") { foreach my $target (sort grep { !$targets{$_}{tags}{not_in_all} } keys %targets) { build($target); } exit; } if ($target eq "all") { foreach my $target (sort keys %targets) { build($target); } exit; } my @matches = grep { /$target/ } sort keys %targets; unless (@matches) { die "No build targets patching pattern /$target/\n"; } if (@matches > 1) { if (scalar(grep { $_ eq $target } @matches) == 1) { @matches = ($target); } else { die "Build pattern is ambiguous, matches multiple targets:\n * " . join("\n * ", @matches) . "\n"; } } build($matches[0]); sub v { return unless $opt_verbose; my $msg = shift; chomp $msg; print STDERR "# $msg\n"; } sub clean { for my $root ("$ENV{GOROOT}/pkg/linux_amd64", "$ENV{GOROOT}/pkg/linux_386") { for my $pkg ("camli") { my $dir = "$root/$pkg"; next unless -d $dir; system("rm", "-rfv", $dir); } } foreach my $target (sort keys %targets) { print STDERR "Cleaning $target\n"; system("make", "-C", $target, "clean"); } } my $did_go_check = 0; sub perform_go_check() { return if $did_go_check++; if ($ENV{GOROOT}) { die "Your \$GOROOT environment variable isn't a directory.\n" unless -d $ENV{GOROOT}; return 1; } # No GOROOT set; see if they have 8g or 6g if (`which 8g` =~ /\S/ || `which 6g` =~ /\S/) { die "You seem to have Go installed, but you don't have your ". "\$GOROOT environment variable set.\n". "Can't build without it.\n"; } die "You don't seem to have Go installed. See:\n\n http://golang.org/doc/install.html\n\n"; } sub build { my @history = @_; my $target = $history[0]; if ($target =~ m!/go/!) { perform_go_check(); } my $already_built = $built{$target} || 0; v("Building '$target' (already_built=$already_built; via @history)"); return if $already_built; $built{$target} = 1; if ($target =~ /^ext:(.+)/) { die "\$GOROOT not set" unless $ENV{GOROOT} && -d $ENV{GOROOT}; my $golib = $1; my @files = glob("$ENV{GOROOT}/pkg/*/${golib}.a"); unless (@files) { die "You need to run:\n\n\tgoinstall $golib\n"; } return; } my $t = $targets{$target} or die "Bogus or undeclared build target: $target\n"; # Add auto-learned dependencies. find_go_camli_deps($target); # Dependencies first. my @deps = @{ $t->{deps} }; if (@deps) { v("Deps of '$target' are @deps"); foreach my $dep (@deps) { build($dep, @history); } v("built deps for $target, now can build"); } my @quiet = ("--silent"); @quiet = () if $opt_verbose; if (system("make", @quiet, "-C", $target, "install") != 0) { my $chain = ""; if (@history > 1) { $chain = "(via chain @history)"; } die "Error building $target $chain\n"; } v("Built '$target'"); if ($opt_test) { opendir(my $dh, $target) or die; my @test_files = grep { /_test\.go$/ } readdir($dh); closedir($dh); if (@test_files) { if (system("make", @quiet, "-C", $target, "test") != 0) { die "Tests failed for $target\n"; } } } } sub find_go_camli_deps { my $target = shift; my $type = ""; if ($target =~ m!lib/go/camli!) { $type = "pkg"; } elsif ($target =~ m!(server|clients)/go\b!) { $type = "cmd"; } else { return; } my $t = $targets{$target} or die "Bogus or undeclared build target: $target\n"; opendir(my $dh, $target) or die; my @go_files = grep { !/_testmain\.go$/ } grep { /\.go$/ } readdir($dh); closedir($dh); # TODO: just stat the files first and keep a cache file of the # deps somewhere (the header of the generated Makefile?) but # maybe it's not worth it. for now we'll parse all the files # every time to find their deps and also generate the Makefile # every time. my @deps; my %seen; # $dep -> 1 for my $f (@go_files) { open(my $fh, "$target/$f") or die; my $src = do { local $/; <$fh>; }; unless ($src =~ m!\bimport\s*\((.+?)\)!s) { die "Failed to parse imports from $target/$f.\n". "No imports(...) block? Um, add a fake one. :)\n"; } my $imports = $1; while ($imports =~ m!"(camli\b.+?)"!g) { my $dep = "lib/go/$1"; push @deps, $dep unless $seen{$dep}++; } } foreach my $dep (@deps) { unless (grep { $_ eq $dep } @{$t->{deps}}) { push @{$t->{deps}}, $dep; } } open(my $mf, ">$target/Makefile") or die; print $mf "\n\n"; print $mf "###### NOTE: THIS IS AUTO-GENERATED FROM build.pl IN THE ROOT; DON'T EDIT\n"; print $mf "\n\n"; print $mf "include \$(GOROOT)/src/Make.inc\n"; if (@deps) { my $pr = ""; foreach my $dep (@deps) { my $cam_lib = $dep; $cam_lib =~ s!^lib/go/!!; $pr .= '$(QUOTED_GOROOT)/pkg/$(GOOS)_$(GOARCH)/' . $cam_lib . ".a\\\n\t"; } chop $pr; chop $pr; chop $pr; print $mf "PREREQ=$pr\n"; } if ($type eq "pkg") { my $targ = $target; $targ =~ s!^lib/go/!!; print $mf "TARG=$targ\n"; } else { my $targ = $target; $targ =~ s!^.*/!!; print $mf "TARG=$targ\n"; } my @non_test_files = grep { !/_test\.go/ } @go_files; print $mf "GOFILES=@non_test_files\n"; print $mf "include \$(GOROOT)/src/Make.$type\n"; close($mf); # print "DEPS of $target: @{ $t->{deps} }\n"; } sub read_targets { my $last; for () { if (m!^\TARGET:\s*(.+)\s*$!) { my $target = $1; $last = $target; $targets{$target} ||= { deps => [] }; next; } s/\#.*//; if (m!^\s+\-\s(\S+)\s*$!) { my $dep = $1; my $t = $targets{$last} or die "Unexpected dependency line: $_"; push @{$t->{deps}}, $dep; next; } if (m!^\s+\=\s*(\S+)\s*$!) { my $tag = $1; my $t = $targets{$last} or die "Unexpected dependency line: $_"; $t->{tags}{$tag} = 1; next; } } #use Data::Dumper; #print Dumper(\%targets); } __DATA__ TARGET: clients/go/camget TARGET: clients/go/camput TARGET: clients/go/camsync TARGET: lib/go/camli/auth TARGET: lib/go/camli/blobref TARGET: lib/go/camli/blobserver TARGET: lib/go/camli/blobserver/handlers TARGET: lib/go/camli/blobserver/localdisk TARGET: lib/go/camli/client TARGET: lib/go/camli/httputil TARGET: lib/go/camli/jsonsign TARGET: lib/go/camli/magic TARGET: lib/go/camli/misc/httprange TARGET: lib/go/camli/mysqlindexer - ext:github.com/Philio/GoMySQL TARGET: lib/go/camli/schema TARGET: lib/go/camli/search TARGET: lib/go/camli/test TARGET: lib/go/camli/test/asserts TARGET: lib/go/camli/webserver TARGET: server/go/blobserver TARGET: server/go/sigserver TARGET: website TARGET: clients/android =not_in_all # too slow