#!/usr/bin/perl # IBM_PROLOG_BEGIN_TAG # This is an automatically generated prolog. # # $Source: src/build/tools/addCopyright $ # # OpenPOWER HostBoot Project # # Contributors Listed Below - COPYRIGHT 2011,2016 # [+] International Business Machines Corp. # [+] 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. # # IBM_PROLOG_END_TAG #################################### ABOUT #################################### # Forked from: # # Author: Mark Jerde (mjerde@us.ibm.com) # # Date: Fri Mar 19 17:40:32 2010 UTC # # # # addCopyright will automatically insert appropriate copyright statements # # in source files following the IBM copyright guidelines (and templates) # # referenced below : # # FSP ClearCase Architecture # # Version 1.9 # # 10/12/2010 # # Editor: Alan Hlava # # # # Section 3.14.1 of the above doc has templates for different files # # # # NOTE: FSP uses the phrase "OCO Source materials" in their copyright # # block, which is classified as 'p1' . We will use the same # # classification here. # # NOTE: to list all files in src EXCEPT the build dir, run: # # make clean # remove autogenerated files # # find src -path 'src/build' -prune -o ! -type d -print | tr '\n' ' ' # # # # addCopyright does not support piping, but you can send these # # to a file, add "addCopyright update" to the beginning of the line, # # and run the file to update all # ############################################################################### use strict; use warnings; use POSIX; use Getopt::Long; use File::Basename; use lib dirname (__FILE__); use Cwd; #------------------------------------------------------------------------------ # Project-specific settings. #------------------------------------------------------------------------------ my $ReleaseYear = `date +%Y`; chomp( $ReleaseYear ); $ReleaseYear = $ENV{'DATE_OVERRIDE'} if defined $ENV{'DATE_OVERRIDE'}; my $copyrightSymbol = ""; # my $copyrightSymbol = "(C)"; # Uncomment if unable to use  character. # set by environment variable in project env.bash my $projectName = $ENV{'PROJECT_NAME'}; my $projectRoot = $ENV{'PROJECT_ROOT'}; # Relative path of import tree from project root my $importPrefix = $ENV{'IMPORT_REL_PATH'}."/"; ## note that these use single ticks so that the escape chars are NOT evaluated yet. my $OLD_DELIMITER_END = 'IBM_PROLOG_END'; my $DELIMITER_END = 'IBM_PROLOG_END_TAG'; my $DELIMITER_BEGIN = 'IBM_PROLOG_BEGIN_TAG'; my $SOURCE_BEGIN_TAG = "\$Source:"; my $SOURCE_END_TAG = "\$"; # Desired License, set by environment variable in project env.bash my $LicenseFile = $ENV{'LICENSE'}; use constant LICENSE_PROLOG => "LICENSE_PROLOG"; #------------------------------------------------------------------------------ # Contributer info #------------------------------------------------------------------------------ # Constants for company's copyright # When adding a new company add constant here and to %fileContributorsCompany use constant IBM => 'International Business Machines Corp.'; use constant GOOGLE => 'Google Inc.'; use constant INSPUR => 'Inspur Power Systems Corp.'; use constant SUPERMICRO => 'Super Micro Computer, Inc.'; use constant YADRO => 'YADRO'; # Create mapping for git contrubitors to companies my %fileContributorsCompany = ( "ibm.com" => IBM, "ozlabs.org" => IBM, "google.com" => GOOGLE, "Google Shared Technology" => GOOGLE, "inspur.com" => INSPUR, "supermicro.com" => SUPERMICRO, "yadro.com" => YADRO, ); #------------------------------------------------------------------------------ # Constants #------------------------------------------------------------------------------ use constant RC_INVALID_PARAMETERS => 1; use constant RC_NO_COPYRIGHT_BLOCK => 2; use constant RC_BAD_CONTRIBUTORS_BLOCK => 3; use constant RC_INVALID_FILETYPE => 4; use constant RC_DIFFERS_FROM_LICENSE_PROLOG => 5; use constant RC_NO_LICENSE_PROLOG_FILE => 6; #------------------------------------------------------------------------------ # Global Vars #------------------------------------------------------------------------------ my $opt_help = 0; my $opt_debug = 0; my $operation = ""; my $opt_logfile = ""; my $DelimiterBegin = ""; my $CopyrightBlock = ""; my $DelimiterEnd = ""; my @CopyRightYears = (); my $copyright_check = 0; my $TempFile = ""; my @Files = (); my $rc = 0; # NOTE: $OLD_DELIMITER_END is a subset of $DELIMITER_END so must match # $DELIMITER_END first in order to return the entire string. my $g_end_del_re = "($DELIMITER_END|$OLD_DELIMITER_END)"; my $g_prolog_re = "($DELIMITER_BEGIN)((.|\n)+?)$g_end_del_re"; ####################################################################### # Main ####################################################################### if (scalar(@ARGV) < 2) { ## needs at least one filename and an operation as a parameter usage(); } my @SaveArgV = @ARGV; #------------------------------------------------------------------------------ # Parse optional input arguments #------------------------------------------------------------------------------ GetOptions( "help|?" => \$opt_help, "validate" => sub { $operation="validate"; }, "update" => sub { $operation="update"; }, "copyright-check" => \$copyright_check, "log-failed-files=s" => \$opt_logfile, "debug" => \$opt_debug, ); ## scan through remaining args and store all files in @Files ## check for old-type parms, just in case foreach ( @ARGV ) { ## print $_; if ( m/^debug$/ ) { $opt_debug = 1; next; } if ( m/^update$/ ) { $operation = $_; next; } if ( m/^validate$/ ) { $operation = $_; next; } push @Files, $_ ; } if ( $opt_debug ) { print STDERR __LINE__, " : ---- DEBUG -----\n"; print STDERR "help = $opt_help\n"; print STDERR "debug = $opt_debug\n"; print STDERR "operation = $operation\n"; print STDERR "log-failed-files = $opt_logfile\n"; ## dump files specified print STDERR "Files:\n"; print STDERR join( ' ', @Files ), "\n"; print STDERR "ReleaseYear = $ReleaseYear\n"; print "\n"; } if ( $operation eq "" ) { print STDOUT "No operation specified\n"; usage(); exit RC_INVALID_PARAMETERS; } if ( ( $opt_logfile ne "" ) ## && ( $operation eq "validate" ) ) { my $logdate = `date +%Y-%m-%d:%H%M`; chomp $logdate; open( LOGFH, "> $opt_logfile" ) or die "ERROR $?: Failed to open $opt_logfile: $!"; print LOGFH "## logfile generated $logdate from command line:\n"; print LOGFH $0, " ", join( ' ', @SaveArgV ); print LOGFH "\nFAILING files:\n"; } ######################################################################## ## MAIN ######################################################################## # Loop through all files and process. foreach ( @Files ) { ## clear global vars $DelimiterBegin = ""; $CopyrightBlock = ""; $DelimiterEnd = ""; @CopyRightYears = (); $rc = 0; ## get filetype my $filetype = filetype($_); print STDOUT "File $_: Type $filetype\n"; ## set Temporary file name. $TempFile = "$_.gitCPYWRT"; if ( $opt_debug ) { print STDERR __LINE__, ": Temporary file name = $TempFile\n"; } ## ## Special case is this file, just return 0 and add copyright manually. ## if ( m/addCopyright/ ) { print STDOUT "---------------------------------------------------------\n"; print STDOUT "Skipping special case file: $_\n"; print STDOUT " Please add the copyright prolog manually.\n"; print STDOUT "---------------------------------------------------------\n"; next; } ## ## Gerrit submissions can include deleted files, just warn and continue if ( ! -e $_ ) { print STDOUT "---------------------------------------------------------\n"; print STDOUT "Skipping deleted file: $_\n"; print STDOUT "---------------------------------------------------------\n"; next; } ## ## Unknown files are valid, but should generate a warning. if ("Unknown" eq $filetype) { print STDOUT "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"; print STDOUT "WARNING:: File $_ :Unknown Filetype: $filetype\n"; print STDOUT " Skipping this file and continuing.\n"; print STDOUT " Please add the copyright prolog manually.\n"; print STDOUT "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"; next; } ## ## text files are valid, but should generate a warning. if (("txt" eq $filetype) || "Initfile" eq $filetype) { print STDOUT "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"; print STDOUT "WARNING:: File $_ : Filetype: $filetype\n"; print STDOUT " Skipping this file and continuing.\n"; print STDOUT " If needed, Please add the copyright \n"; print STDOUT " prolog manually.\n"; print STDOUT "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"; next; } # Skip PRD simulator test case files. if ( "PrdTestCaseFile" eq $filetype ) { print STDOUT < Prolog not correct for $filename.\n"; print STDOUT "To fix run: git show --pretty=\"format:\" --name-only | xargs addCopyright update\n"; print STDOUT "\nDiff:\n"; print STDOUT `diff $blockFile $licenseFile`; my $relLicensePath = $LicenseFile; $relLicensePath =~ s/$projectRoot//; print STDOUT "\nWARNING: Copyright block does not match LICENSE_PROLOG file found at $relLicensePath\n"; system("rm -f $blockFile $licenseFile"); return RC_DIFFERS_FROM_LICENSE_PROLOG; } } else { print STDOUT "WARNING: Missing LICNESE_PROLOG file in directory structure\n"; return RC_NO_LICENSE_PROLOG_FILE; } return 0; } sub createYearString($) { my ( $filename ) = @_; # The list of years starts with the years in the current prolog and the # current release year. my @years = (); # Get size of array @CopyRightYears my $size = @CopyRightYears; if ( $size < 2 ) ## If less than 2 then date entry has only a year mentioned { @years = @CopyRightYears; # add current year if not already in list if ( ($years[0] ne $ReleaseYear) ) { push @years, $ReleaseYear ; } } else { @years = ( @CopyRightYears, $ReleaseYear ); } ## Make a call to git to find the earliest commit date of the file ## new files will not have a log, so the "git log" call above will ## return nothing. ## Push any year we find onto the @years array my $cmd = "git log -- $filename | grep Date: | tail -n 1"; ## print "run $cmd\n"; my @logstrings = split( " ", `$cmd` ); if ( $? ) { die "ERROR $? : Could not run $cmd $!\n"; } if ( scalar(@logstrings) >= 5 ) { push @years, $logstrings[5] ; } # Get a unique list of years and sort the list. my %tmp = map { $_, 1 } @years; @years = sort( keys %tmp ); if ( $opt_debug ) { print STDERR __LINE__, ": years: ", join( ',', @years ), "\n"; } # Get the fist and last years in the list. my $year_str = ""; $year_str = shift @years; $year_str .= ',' . pop @years if ( @years ); return $year_str; } ################################### ## Helper function for removeCopyrightBlock() ################################### sub removeProlog { my ( $data, $begin_re, $end_re ) = @_; $data =~ s/(\n?)(.*?$begin_re.*$g_prolog_re(.|\n)*?$end_re.*?\n)/$1/; return $data; } ################################### ## remove old Copyright Block in preparation for making a new one. ## makes up a debug file named ".remove" ################################### sub removeCopyrightBlock { my ( $filename, $filetype ) = @_ ; my $data = "" ; ## Modify file in place with temp file Perl Cookbook 7.8 my $savedbgfile = "$filename.remove"; system( "cp -p $filename $TempFile" ); ## preserve file permissions open( INPUT, "< $filename" ) or die " $? can't open $filename: $!" ; read( INPUT, $data, -s INPUT ) or die "ERROR $? : reading $filename: $!"; close( INPUT ) or die " $? can't close $filename: $!" ; open( OUTPUT, "> $TempFile" ) or die " $? can't open $TempFile: $!" ; select( OUTPUT ); ## new default filehandle for print ## preprocess to get rid of OLD_DELIMITER_END $data =~ s/$OLD_DELIMITER_END(\s+?)/$DELIMITER_END$1/; if ( "C" eq $filetype ) { ## pre-process this for /* */ comments $data = removeProlog( $data, '\/\*', '\*\/' ); ## Now apply filter for // comments $data = removeProlog( $data, '\/\/', '' ); } elsif ( ("RPC" eq $filetype) or ("LinkerScript" eq $filetype) ) { $data = removeProlog( $data, '\/\*', '\*\/' ); } elsif ( $filetype eq "xml" ) { $data = removeProlog( $data, '' ); } elsif ( $filetype eq "xsl" ) { $data = removeProlog( $data, '' ); } elsif ( "Assembly" eq $filetype ) { $data = removeProlog( $data, '\#', '' ); } elsif ( ("Autoconf" eq $filetype) or ("Automake" eq $filetype) or ("CVS" eq $filetype) or ("Makefile" eq $filetype) or ("Perl" eq $filetype) or ("PrdRuleFile" eq $filetype) or ("Python" eq $filetype) or ("Shellscript" eq $filetype) or ("Tcl" eq $filetype) ) { # Don't wipe the the '#!' line at the top. $data = removeProlog( $data, '\#', '' ); } else { print STDOUT "ERROR: Don't know how to remove old block from $filetype file.\n"; close OUTPUT; return RC_INVALID_FILETYPE; } print OUTPUT $data; ## finish up the files close( OUTPUT ) or die " $? can't close $TempFile: $!" ; rename( $filename, "$savedbgfile" ) or die " $? can't rename $filename: $!" ; rename( $TempFile, $filename ) or die " $? can't rename $TempFile: $!" ; if ( !$opt_debug ) { ## leave the files around for debug unlink( $savedbgfile ) or die " $? can't delete $savedbgfile: $!"; } } ################################### ## Add an empty copyright block to the file, for example (C/C++ files): ## ## // IBM_PROLOG_BEGIN_TAG IBM_PROLOG_END_TAG ## ## - The block will be filled-in in the next step. ## ## @param[in] - filename to modify ## @param[in] - filetype ## @param[in] - returncode from validate ## ## @return none ## ## - Makes up a debug file called ".empty" ################################## sub addEmptyCopyrightBlock { my ( $filename, $filetype, $validaterc ) = @_; my $line; ## Modify file in place with temp file Perl Cookbook 7.8 my $savedbgfile = "$filename.empty"; system( "cp -p $filename $TempFile" ) ; ## preserve permissions open( INPUT, "< $filename" ) or die " $? can't open $filename: $!" ; open( OUTPUT, "> $TempFile" ) or die " $? can't open $TempFile: $!" ; select( OUTPUT ); ## new default filehandle for print if (("Autoconf" eq $filetype) or ("Automake" eq $filetype) or ("CVS" eq $filetype) or ("Perl" eq $filetype) or ("Python" eq $filetype) or ("Shellscript" eq $filetype) or ("Tcl" eq $filetype)) { ## All files with a "shebang" at the beginning $line = ; # Keep the '#!' line at the top. ## The following says : if the first line is a "shebang" line ## (e.g. "#!/usr/bin/perl"), then print it _before_ the copyright ## block, otherwise (the unless line), print it _after_ the copyright ## block. if ($line =~ m/^#!/) { print OUTPUT $line; } print OUTPUT "$DELIMITER_BEGIN $DELIMITER_END\n"; unless ($line =~ m/^#!/) { print OUTPUT $line; } } else { print OUTPUT "$DELIMITER_BEGIN $DELIMITER_END\n"; } # Copy rest of file while (defined($line = )) { print OUTPUT $line; } ## finish up the files close( INPUT ) or die " $? can't close $filename: $!" ; close( OUTPUT ) or die " $? can't close $TempFile: $!" ; rename( $filename, "$savedbgfile" ) or die " $? can't rename $filename: $!" ; rename( $TempFile, $filename ) or die " $? can't rename $TempFile: $!" ; if ( !$opt_debug ) { ## leave the files around for debug unlink( $savedbgfile ) or die " $? can't delete $savedbgfile: $!"; } } ############################################ ## Helper functions for fillinEmptyCopyrightBlock() ############################################ sub addPrologComments { my ( $data, $begin, $end ) = @_; my @lines = split( /\n/, $data ); $data = ''; for my $line ( @lines ) { # If there is an block comment end tag, fill the end of the line with # spaces. if ( $end ) { my $max_line_len = 70; my $len = length($line); if ( $len < $max_line_len ) { my $fill = ' ' x ($max_line_len - $len); $line .= $fill; } } # NOTE: CMVC prologs with inline comments will have a single trailing # space at the end of the line. This is undesirable for most # developers so it will not be added. if ( $line =~ m/$DELIMITER_BEGIN/) { $line = "$line $end" if ( $end ); $line = "$begin $line\n"; } elsif ( $line =~ m/$DELIMITER_END/ ) { $line = "$line $end" if ( $end ); $line = "$begin $line"; } else { if ( not $end and not $line ) { # Compensate for blank lines with no end delimeter. $line = "$begin\n"; } else { $line = "$begin $line"; $line = "$line $end" if ( $end ); $line = "$line\n"; } } $data .= $line; } return $data; } ############################################ ## Generates final copyright block ## ## @parma[in] filename ## ## @return final copyright block string ############################################ sub genCopyrightBlock { my ($filename, $filetype) = @_; # Get the copyright data that is required in the license file. my $copyrightStr = "Contributors Listed Below - COPYRIGHT"; my $copyrightYear = createYearString( $filename ); # Get copyright contributors based on hash so no duplicates my %fileContributors = getFileContributors( $filename ); my $copyright_Contributors = ""; foreach my $key (sort keys %fileContributors) { $copyright_Contributors .= "[+] ".$key."\n"; } ## Get desired license my $LicenseContent = ""; open(LICENSE, "< $LicenseFile") or die " $? can't open $LicenseFile: $!" ; while (my $line = ) { my $evalLine = eval "qq($line)"; $LicenseContent .= $evalLine; } close(LICENSE); ## define the final copyright block template here. my $IBMCopyrightBlock = <'); } elsif ( "xsl" eq $filetype) { $IBMCopyrightBlock = addPrologComments($IBMCopyrightBlock, ''); } else { print STDOUT "ERROR: Can\'t handle filetype: $filetype\n"; return RC_INVALID_FILETYPE; } return $IBMCopyrightBlock; } ############################################ ## fill in the empty copyright block ## Copyright guidelines from: ## FSP ClearCase Architecture ## Version 1.9 ## 10/12/2010 ## Editor: Alan Hlava ## ## Section 3.14.1 has templates for different files ## ############################################ sub fillinEmptyCopyrightBlock { my ( $filename, $filetype ) = @_; ## define the final copyright block template here. my $IBMCopyrightBlock = genCopyrightBlock($filename,$filetype); ## Modify file in place with temp file Perl Cookbook 7.8 my $savedbgfile = "$filename.fillin"; system( "cp -p $filename $TempFile" ); ## preserve file permissions open( INPUT, "< $filename" ) or die " $? can't open $filename: $!" ; my $newline; my $lines = ""; while ( defined($newline = ) ) { $lines .= $newline; } # Replace existing block with the current content. $lines =~ s/$DELIMITER_BEGIN[^\$]*$DELIMITER_END/$IBMCopyrightBlock/s; open( OUTPUT, "> $TempFile" ) or die " $? can't open $TempFile: $!" ; select( OUTPUT ); ## new default filehandle for print print OUTPUT $lines; ## finish up the files close( INPUT ) or die " $? can't close $filename: $!" ; close( OUTPUT ) or die " $? can't close $TempFile: $!" ; rename( $filename, "$savedbgfile" ) or die " $? can't rename $filename: $!" ; rename( $TempFile, $filename ) or die " $? can't rename $TempFile: $!" ; if ( !$opt_debug ) { ## leave the files around for debug unlink( $savedbgfile ) or die " $? can't delete $savedbgfile: $!"; } } ####################################### ## Gets file contirbutors based on git log of a file ## ## @parma[in] filename ## ## @return hash of contributors (key,value) => (name/company, 1) ####################################### sub getFileContributors { my ( $filename ) = @_; # Create a "set like" hash for file contributors to handle duplicates # so key is the only important information my %fileContributors = (); # Check file for company Origin my $gitDomain = `git log --follow --find-copies-harder -C85% -M85% -- $filename | grep Origin: | sort | uniq`; my @gitDomain = split('\n', $gitDomain); foreach my $origin (@gitDomain) { chomp($origin); # Remove all characters through word "Origin:" $origin =~ s/[^:]*\://; # Remove white space after colon $origin =~ s/^\s+//; if (exists($fileContributorsCompany{$origin})) { # Add company info for copyright contribution $fileContributors{$fileContributorsCompany{$origin}} = 1; } } # Check file for all contributors my $gitAuthors = `git log --follow --find-copies-harder -C85% -M85% --pretty="%aN <%aE>" -- $filename | sort | uniq`; my @gitAuthors = split('\n', $gitAuthors); # Get commit's author # If running copyright_check run 'git log' as a commit is not taking place # Else we currently have no way of getting the current author. Because # this is a pre-commit hook, the commit staged to be committed does not # show up in 'git log' until commit has completed. We cannot look up # current user's info because the user pushing the commit may not be # the author. if($copyright_check) { my $curAuthorEmail = `git log -n1 --pretty=format:"%aN <%aE>"`; chomp($curAuthorEmail); push(@gitAuthors, $curAuthorEmail); } # Internal mirror, add IBM as contributor elsif (defined $ENV{'MIRROR'}) { push(@gitAuthors, "\@ibm.com>"); } foreach my $contributor (@gitAuthors) { my $companyExists = 0; chomp($contributor); # Grab company domain out of contributor's email my $domain = substr ($contributor, index($contributor, '@') + 1, -1); # Due to multiple prefixes for IBM like us, in, linux, etc will try # removing characters up to each period (.) until correct domain # address found my @domainSections = split(/\./,$domain); for (my $i = 0; $i < @domainSections; $i++) { if (exists($fileContributorsCompany{$domain})) { $companyExists = 1; last; } # Remove all characters upto & including the first period (.) seen $domain =~ s/[^.]*\.//; } #Check if contributor's company exists if ($companyExists) { # Add company info for copyright contribution $fileContributors{$fileContributorsCompany{$domain}} = 1; } else { my $name = substr ($contributor, 0, index($contributor, '<') -1); if($name) { # Add individual info for copyright contribution $fileContributors{$name} = 1; } else { die("Cannot find name of contributor in git commit"); } } } return %fileContributors; }