diff -r 000000000000 -r 83f4b4db085c bldsystemtools/commonbldutils/SFUpdateLicenceHeader.pl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bldsystemtools/commonbldutils/SFUpdateLicenceHeader.pl Tue Feb 02 01:39:43 2010 +0200 @@ -0,0 +1,2634 @@ +# Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). +# This component and the accompanying materials are made available +# under the terms of the License "Eclipse Public License v1.0" +# which accompanies this distribution, and is available +# at the URL "http://www.eclipse.org/legal/epl-v10.html". +# +# Initial Contributors: +# Nokia Corporation - initial contribution +# +# Contributors +# +# Description: Replace S60 header with Symbian Foundation license header. +# Output file (results) is compatibe for SFMakeLxrLinks.pl as input. +# +use strict; +use File::Find; +use File::Basename; +use Getopt::Long; +use IO::Handle; +use FindBin qw($Bin); +use FileHandle; + +#################### +# Constants +#################### + +# Tool version +use constant VERSION => '2.1'; +# Version history: 0.8 Added copyright year pick-up +# Version history: 0.9- Bug fixesg +# Version history: 0.95- EPL header support added +# Version history: 0.96 Minor script adjustments +# Version history: 0.97 Assembly files (.s, .cia, .asm) checked as well +# Version history: 0.98 Support for -oem added. Also @file tag removed from template +# Version history: 0.99 Testing -oem option +# Version history: 1.0 Comment column added for PostProcess script +# Version history: 1.01 Modify option bug fixed +# Version history: 1.1 Description bug fixed +# Version history: 1.2 Digia copyrights moved to SF as well. Also R/O attribute removed only for files modified +# Version history: 1.3 Distribution policy files handled as well. With -create also created +# Version history: 1.31 Fixes to distribution file handling (only non-empty directories acknowledged) +# Version history: 1.32 .pm files checked as well +# Version history: 1.4 Bug fixes and "ignorefile" agrument added +# Version history: 1.41 Bug fix in Description pick-up +# Version history: 1.42 Bug fix in -ignore option (also missing .s60 file creation need to be ignored). Default value set to ignore option +# Version history: 1.43 Description statistics fixed, .hpp added, description pick-up improved +# Version history: 1.5 -verify option implemented, ignorefile default value extended, statistics go to log +# Version history: 1.51 Copyright year pick-up bug fixed, ignorefilepattern comparison case-insensitive +# Version history: 1.52 current s60 dumped to result +# Version history: 1.53 abld.bat added ti ignorefile default +# Version history: 1.54 -verify statistics improved +# Version history: 1.55 -eula option added +# Version history: 1.56 .mmh files added, extra Non-Nokia check added for "No Copyright" case for weired headers +# Version history: 1.57 Changes to -verify +# Version history: 1.58 @echo on ... @echo off added to .cmd and .bat headers +# Version history: 1.59 EPL warning log entry --> info +# Version history: 1.60 and 1.61 -ignorelist option added +# Version history: 1.62 Uppercase REM text allowed +# Version history: 1.63 Internal directory check added to -verify +# Version history: 1.64 Symbian --> Symbian.*Ltd in $ExternalToNokiaCopyrPattern +# Version history: 1.65 Bug fixed in normalizeCppComment +# Version history: 1.70 Changes to better cope with ex-Symbian sources, +# Pasi's better "@rem" taken into use for .bat and .cmd files +# Version history: 1.71 Config file support added (option -config) for non 3/7 IDs +# Version history: 1.72 handleVerify checks improved to include also file start +# Version history: 1.73 \b added to Copyright word to reduce if "wrong" alarms +# Version history: 1.74 incomplete copyright check added to -verify +# Version history: 1.75 Support for ignoring generated headers (@sfGeneratedPatternArray) added +# Version history: 1.76 .script extension added (using Cpp comments e.g. // Text) +# Version history: 1.77 Reporting and logging improvements for wk19 checks (need to check / patch single files) +# Version history: 1.80 Few Qt specific file extensions added, -lgpl option added, +# C++ comment fix in handleOem +# Version history: 1.90 checkNoMultipleLicenses function added, and call to handleVerify* added +# Version history: 2.0 handleDistributionValue() changes IDs 0-->3/7 and 3-->7, +# isGeneratedHeader() checks for file content added. +# Version history: 2.01 checkPortionsCopyright implemented and applied +# Version history: 2.02 Extra license word taken out from EPL header +# Version history: 2.1 -verify -epl support added and switchLicense() tried first for SFL --> EPL switching + +my $IGNORE_MAN ='Ignore-manually'; +my $IGNORE ='Ignore'; +my $INTERNAL = 'internal'; +use constant KEEP_SYMBIAN => 0; +use constant REMOVE_SYMBIAN => 1; + + +#file extention list that headers should be replace +my @extlist = ('.cpp', '.c', '.h', '.mmp', '.mmpi', '.rss', '.hrh', '.inl', '.inf', '.iby', '.oby', + '.loc', '.rh', '.ra', '.java', '.mk', '.bat', '.cmd', '.pkg', '.rls', '.rssi', '.pan', '.py', '.pl', '.s', '.asm', '.cia', + '.s60', '.pm', '.hpp', '.mmh', '.script', + '.pro', '.pri'); # Qt specific + +# Various header comment styles +my @header_regexps = +( +'^\s*(\#.*?\n)*', # Perl, Python +'^\s*(\@echo\s*off\s*\n)?\n*(@?(?i)rem.*?\n)*(\@echo\s*on\s*)?', # Windows command script +'^\s*(\;.*?\n)*', # SIS package file +'\s*\/\*[\n\s\*-=].*?\*\/', # C comment block +'(\s*\/\/.*\n)+', # C++ comment block (do not use /s in regexp evaluation !!!) +'^\s*((\/\/|\#).*?\n)*' # Script file comment +); + +# Comment regular expression (Indeces within @header_regexps) +use constant COMMENT_PERL => 0; +use constant COMMENT_CMD => 1; +use constant COMMENT_SIS_ASM => 2; +use constant COMMENT_C => 3; +use constant COMMENT_CPP => 4; +use constant COMMENT_SCRIPT => 5; + +my $descrTemplateOnly = '\?Description'; +my $linenumtext = "1"; # Use this linenumer in LXR links + +# Copyright patterns +my $copyrYearPattern = 'Copyright\b.*\d{4}\s*([,-]\s*\d{4})*'; +my $copyrYearPattern2 = '\d{4}(\s*[,-]\s*\d{4})*'; +use constant DEFCOPYRIGHTYEAR => "2009"; # For error cases + +my $NokiaCopyrPattern = '\bCopyright\b.*Nokia.*(All Rights)?'; +my $NonNokiaCopyrPattern = '\bCopyright\b.*(?!Nokia).*(All Rights)?'; +my $CopyrPattern = '\bCopyright\b'; +my $RemoveS60TextBlockPattern = 'This material.*Nokia'; +my $CC = 'CCHAR'; +my $BeginLicenseBlockPattern = 'BEGIN LICENSE BLOCK'; +my $OldNokiaPattern = 'Nokia\s*Corporation[\.\s]*\n'; +my $NewNokiaText = "Nokia Corporation and/or its subsidiary(-ies).\n"; # Used in substitu to text +my $NewNokiaPattern = "Nokia Corporation and/or its subsidiary"; +my $OldNokiaPattern2 = "This material.*including documentation and any related.*protected by copyright controlled.*Nokia"; +my $PortionsNokiaCopyrPattern = 'Portions.*Copyright\b.*Nokia.*(All Rights)?'; +my $NewPortionsNokiaCopyrPattern = 'Portions\s*Copyright\b.*' . $NewNokiaPattern; + +# Move these copyrights to Nokia !!! +my $ExternalToNokiaCopyrPattern = 'Copyright\b.*(Symbian\sLtd|Symbian\s*Software\s*Ltd|Digia|SysopenDigia).*(All\s+Rights|All\s+rights)?'; +my $PortionsSymbianCopyrPattern = 'Portions\s*Copyright\b.*(Symbian\sLtd|Symbian\s*Software\s*Ltd).*(All\s+Rights|All\s+rights)?'; + +############### +# SFL headers +############### +# SFL C/C++ style +# +my $SFLicenseHeader = +'/* +* Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). +* All rights reserved. +* This component and the accompanying materials are made available +* under the terms of the License "Symbi'.'an Foundation License v1.0" +* which accompanies this distribution, and is available +* at the URL "http://www.symbi'.'anfoundation.org/legal/sf'.'l-v10.html". +* +* Initial Contributors: +* Nokia Corporation - initial contribution. +* +* Contributors: +* +* Description: +* +*/'. "\n"; + +# Test string to test header have been changed +my $SFLHeaderTest = 'Symbian\s*Foundation\s*License.*www\.symbianfoundation\.org\/legal\/sfl'; + +# Partial SFL header template (# will be replaced by actual comment syntax char) +# Prepare for cases where someone adds spaces to string. +my $SFLicenseHeaderPartial_template = +$CC . '\s*This\s*component\s*and\s*the\s*accompanying\s*materials\s*are\s*made\s*available\s*\n' . +$CC . '\s*under\s*the\s*terms\s*of\s*the\s*License\s*\"Symbian\s*Foundation\s*License\s*v1\.0\"\s*\n' . +$CC . '\s*which\s*accompanies\s*this\s*distribution\,\s*and\s*is\s*available\s*\n' . +$CC . '\s*at\s*the\s*URL\s*\"http\:\/\/www\.symbianfoundation\.org\/legal\/sfl\-v10\.html\"\s*\.'; + +# SFL other comment styles (replace # with actual comment starter) +# +my $SFLicenseHeader_other_template = +'# +# Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). +# All rights reserved. +# This component and the accompanying materials are made available +# under the terms of the License "Symbi'.'an Foundation License v1.0" +# which accompanies this distribution, and is available +# at the URL "http://www.symbi','anfoundation.org/legal/sf'.'l-v10.html". +# +# Initial Contributors: +# Nokia Corporation - initial contribution. +# +# Contributors: +# +# Description: +# +'; + + + +############### +# EPL headers +############### +# C/C++ style +my $EPLLicenseHeader = +'/* +* Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). +* All rights reserved. +* This component and the accompanying materials are made available +* under the terms of "Eclipse Public License v1.0" +* which accompanies this distribution, and is available +* at the URL "http://www.eclipse.org/legal/epl-v10.html". +* +* Initial Contributors: +* Nokia Corporation - initial contribution. +* +* Contributors: +* +* Description: +* +*/' . "\n"; + +# Test string to test header have been changed +my $EPLHeaderTest = 'Eclipse\s*Public\s*License.*www\.eclipse\.org\/legal\/epl'; + +# Partial EPL header (replace # with actual comment starter) +# Prepare for cases where someone adds spaces to string. +my $EPLLicenseHeaderPartial_template = +$CC . '\s*This\s*component\s*and\s*the\s*accompanying\s*materials\s*are\s*made\s*available\s*\n' . +$CC . '\s*under\s*the\s*terms\s*of\s*\"Eclipse\s*Public\s*License\s*v1\.0\"\s*\n' . +$CC . '\s*which\s*accompanies\s*this\s*distribution,\s*and\s*is\s*available\s*\n' . +$CC . '\s*at\s*the\s*URL\s*\"http\:\/\/www\.eclipse\.org\/legal\/epl\-v10\.html\"\s*\.'; + + +# EPL other comment styles (replace # with comment starter) +# +my $EPLLicenseHeader_other_template = +'# +# Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). +# All rights reserved. +# This component and the accompanying materials are made available +# under the terms of "Eclipse Public License v1.0" +# which accompanies this distribution, and is available +# at the URL "http://www.eclipse.org/legal/epl-v10.html". +# +# Initial Contributors: +# Nokia Corporation - initial contribution. +# +# Contributors: +# +# Description: +# +'; + +############## +# LGPL headers +############## +my $LGPLLicenseHeader = +'/* +* Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). +* All rights reserved. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, version 2.1 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, +* see "http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html/". +* +* Description: +* +*/'; + +# Test string to test header have been changed +my $LGPLHeaderTest = 'GNU\s*Lesser\s*General\s*Public\s*License.*www\.gnu\.org\/licenses/old-licenses\/lgpl-2\.1\.html'; + +# Partial LGPL header (replace $CC with actual comment starter) +my $LGPLLicenseHeaderPartial_template = +$CC . '\s*This\s*program\s*is\s*free\s*software\:\s*you\s*can\s*redistribute\s*it\s*and\/or\s*modify\n' . +$CC . '\s*it\s*under\s*the\s*terms\s*of\s*the\s*GNU\s*Lesser\s*General\s*Public\s*License\s*as\s*published\s*by\n' . +$CC . '\s*the\s*Free\s*Software\s*Foundation\,\s*version\s*2\.1\s*of\s*the\s*License\n'; + + +# LGPL other comment styles (replace # with comment starter) +# +my $LGPLLicenseHeader_other_template = +'# +# Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). +# All rights reserved. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, version 2.1 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, +# see "http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html/". +# +# Description: +# +'; + + +############### +# S60 headers +############### +# C/C++ style +my $S60HeaderPartial_template = +$CC . " This material, including documentation and any related computer\n" . +$CC . " programs, is protected by copyright controlled by Nokia. All\n" . +$CC . " rights are reserved. Copying, including reproducing, storing\n" . +$CC . " adapting or translating, any or all of this material requires the\n" . +$CC . " prior written consent of Nokia. This material also contains\n" . +$CC . " confidential information which may not be disclosed to others\n" . +$CC . " without the prior written consent of Nokia."; + +# Test string to test header have been changed +my $S60HeaderTest = 'is\s*protected\s*by\s*copyright\s*controlled\s*by\s*Nokia'; + + +my @SflHeaders = (\$SFLicenseHeader, \$SFLicenseHeader_other_template, \$SFLicenseHeaderPartial_template, \$SFLHeaderTest); # contains refs +my @EplHeaders = (\$EPLLicenseHeader, \$EPLLicenseHeader_other_template, \$EPLLicenseHeaderPartial_template, \$EPLHeaderTest); # contains refs +my @S60Headers = (undef,undef,\$S60HeaderPartial_template, \$S60HeaderTest); # contains refs +my @LgplHeaders = (\$LGPLLicenseHeader, \$LGPLLicenseHeader_other_template, \$LGPLLicenseHeaderPartial_template, \$LGPLHeaderTest); # contains refs + +# Header styles (indeces within @SflHeaders and @EplHeaders) +use constant CPP_HEADER => 0; +use constant OTHER_HEADER => 1; + +# switchLicense related values +use constant SFL_LICENSE => 3; +use constant EPL_LICENSE => 7; +use constant S60_LICENSE => 0; +use constant LGPL_LICENSE => 4; +use constant LICENSE_CHANGED => 1; +use constant LICENSE_NONE => 0; +use constant LICENSE_ERROR => -1; +use constant LICENSE_NOT_SUPPORTED => -2; + +# Distribution policy file values +use constant INTERNAL_DISTRIBUTION_VALUE => "1"; +use constant ZERO_DISTRIBUTION_VALUE => "0"; +use constant SFL_DISTRIBUTION_VALUE => "3"; +use constant EPL_DISTRIBUTION_VALUE => "7"; # option -epl +use constant TSRC_DISTRIBUTION_VALUE => "950"; # +use constant NONSF_DISTRIBUTION_VALUE => "Other"; # +use constant DISTRIBUTION_FILENAME => "distribution.policy.s60"; + +my $usage = 'SFUpdateLicenHeader.pl tool, version: ' . VERSION . +' +Usages: + perl SFUpdateLicenceHeader.pl [-modify] [-epl] [-oem] [-create] [-ignorefile pattern] + [-output csv-file] [-log logfile] [-verbose level] [-verify] [-append] + [-oldoutput old-csv-file] DIRECTORY|FILE + + Check switch to SFL header in a directory (and subdirectories under that): + perl SFUpdateLicenceHeader.pl -output csv-file -log logfile DIRECTORY + Switch to SFL header and modify .policy.s60 files in a directory (and subdirectories under that): + perl SFUpdateLicenceHeader.pl -modify -output csv-file -log logfile DIRECTORY + Switch to SFL header and modify .policy.s60 files in a single file: + perl SFUpdateLicenceHeader.pl -modify -output csv-file -log logfile FILE + Switch to EPL header and modify .policy.s60 files: + perl SFUpdateLicenceHeader.pl -modify -epl -output csv-file -log logfile DIRECTORY + Switch to SFL header and modify/create missing .policy.s60 files: + perl SFUpdateLicenceHeader.pl -modify -create -output csv-file -log logfile DIRECTORY + Switch to SFL header and ignore files matching CCM file pattern: + perl SFUpdateLicenceHeader.pl -modify -ignore "_ccmwaid.inf" -output csv-file -log logfile DIRECTORY + Switch to SFL header and ignore files matching CCM or SVN file patterns: + perl SFUpdateLicenceHeader.pl -modify -ignore "(_ccm|.svn)" -output csv-file -log logfile DIRECTORY + Switch back to Nokia header (for OEM delivery team): + perl SFUpdateLicenceHeader.pl -modify -oem -output csv-file -log logfile DIRECTORY + Verify file header changes + perl SFUpdateLicenceHeader.pl -verify -output csv-file -log logfile DIRECTORY + Verify and append logs and results to single file + perl SFUpdateLicenceHeader.pl -verify -append -output AllResults.csv -log AllLogs.log DIRECTORY + Verify file header changes and use old result file as add-on configuation (used by -verbose only) + perl SFUpdateLicenceHeader.pl -verify -oldoutput old-csv-file -output csv-file -log logfile DIRECTORY + +For more info, see http://s60wiki.nokia.com/S60Wiki/SFDG-File-Header#SFUpdateLicenceHeader.pl +'; + +# Logging constants +use constant LOG_ALWAYS => 0; +use constant LOG_INFO => 3; +use constant LOG_ERROR => 1; +use constant LOG_WARNING => 2; +use constant LOG_DEBUG => 4; +my @LOGTEXTS = ("", "ERROR: ", "Warning: ", "Info: ", "DEBUG: "); +my $sep = ","; +my $logFile = ""; + +# Issue categories in result CSV formatted file +use constant HEADER_CONTEXT => 'header-issue'; +use constant DISTRIBUTION_CONTEXT => 'distribution-issue'; + + +#################### +# Global variables +#################### + +# Command line options +my $help = 0; +my $outputfile; +my $ignorefilepattern; # File patterns to ignore +# my $optSfl = 1; # By default sfl is on +my $optEpl = 0; # Use EPL headers +my $optLgpl = 0; # Use LGPL v2.1 headers +my $optLogLevel = LOG_INFO; +my $optModify = 0; # (default mode is check) +my $optCreate = 0; # (create missing files) +my $optOem = 0; # OEM delivery to S60 license +my $optAppend = 0; # Append results +my $optVerify = 0; # Verify option +my $oldOutputFile; # Version 1.60 old output file in CSV format +my %manualIgnoreFileHash; # Hash of files ignored +my $optDescription = 0; # Output also missing description +my $optOutputOK = 0; # Output also OK issues for -verify + +# The last distribution ID +my $lastDistributionValue = ""; + +# Config file specific gllobals +my $configFile; +my $configVersion = ""; +my @sfDistributionIdArray = (); # Distribution ID array +my @sfGeneratedPatternArray = (); # Distribution ID array + +# +# Statistics variables +# +my $fileCount = 0; +my $modifiedFileCount = 0; +my $willModifiedFileCount = 0; +my $noDescrcount = 0; +my $otherCopyrCount=0; +my $ExternalToNokiaCopyrCount=0; +my $NokiaCopyrCount=0; +my $NoCopyrCount=0; +my $UnclearCopyrCount=0; +my $SflToS60Changes = 0; +my $EplToS60Changes = 0; +my $SflToEplChanges = 0; +my $EplToSflChanges = 0; +my $LicenseChangeErrors = 0; +my $ignoreCount = 0; +my $unrecogCount = 0; +my $createCount = 0; +my @verifyFailedCount = (0,0,0,0,0,0,0,0,0,0,0); +my @verifyFailedCountMsgs = ("Distribution file missing", # Index 0 + "SFL or EPL distribution ID missing", #1 + "SFL or EPL header missing", #2 + "Proper copyright missing", #3 + "Header vs. distribution ID mismatch", #4 + "Internal directory going to SF", #5 + "Old Nokia file header used", #6 + "Unclear Non-Nokia copyright", #7 + "Incomplete copyright", #8 + "OK", #9 + "OK (Non-Nokia)", #10 + "Multiple license" #11 + ); +use constant VERI_MISSING_FILE => 0; +use constant VERI_MISSING_ID => 1; +use constant VERI_MISSING_HEADER => 2; +use constant VERI_PROPER_COPYRIGHT => 3; +use constant VERI_ID_HEADER_MISMATCH => 4; +use constant VERI_INTERNAL_TO_SF => 5; +use constant VERI_OLD_NOKIA_HEADER => 6; +use constant VERI_UNCLEAR_COPYR => 7; +use constant VERI_INCOMPLETE_COPYR => 8; +use constant VERI_OK => 9; +use constant VERI_OK_NON_NOKIA => 10; +use constant VERI_MULTIPLE_LICENSES => 11; + + +################################## +# Callback for the find function +# (wanted) +# Note ! "no_chdir" not used +################################## +sub process_file +{ + + my $full_filename = $File::Find::name; # Full name needed for result and logs ! + $full_filename =~ s/\\/\//g; # Standardize name + my $filename = $_; # This in filename in the current working directory ! + + #Skip all directory entries + return if -d; + + if ($ignorefilepattern && $full_filename =~ m/$ignorefilepattern/i) + { + printLog(LOG_DEBUG, "File ignored by pattern: ". $full_filename . "\n"); + $ignoreCount++; + return; + } + + # Set initial value from options, turn off later if needed + my $modify = $optModify; + my $willmodify = 1; # For statistics only + + #skip non-source code files + my ($name, $path, $suffix)=fileparse($_, qr/\.[^.]*/); + + my $match = grep {$_ eq lc($suffix)} @extlist; + if (!$match) + { + printLog(LOG_DEBUG, "File ignored: ". $full_filename . "\n"); + $ignoreCount++; + return; + } + + # As there have been cased where e.g. .pkg file has been saved as Unicode format + # Check that we can really modify file (e.g. Unicode files not supported) + if (! (-T $filename)) # Text file only ! + { + printLog(LOG_WARNING, "File not in text format: $full_filename\n"); + return; + } + + + printLog(LOG_DEBUG, "Handling ". $full_filename . "\n"); + + local($/, *FH); + + # Open file for reading here, re-open later if modified + open(FH, "<$filename") or return printLog(LOG_ERROR, "Failed to open file for reading: $full_filename\n"); + + my $filecontent = ; # read whole content into buffer + # Standardize the new-line handling in files by replacing \r with \n + # Some files may be using only \r and it causes problems + $filecontent =~ s/\r/\n/g; + + my $modifiedFilecontent; + my $description = ""; + my $contributors = ""; + + #comment mark + my $cm = '\*'; + my $cm2 = '*'; + my $newheader = ""; + my $oldheader = ""; + my $oldheader2; + my $header_regexp = ""; + my $header_regexp2; + my $isCcomment = 0; + my $isCPPcomment = 0; + my $oldCopyrightYear; + my $matchPos1; + my $matchPos2; + my $unrecog=0; + + + # For statisctics.... + $fileCount++; + + ################### + # Prepare regular expressions + # based on file extensions + ################### + + if (lc($suffix) eq ".s60") + { + # + # Alter exisring distribution policy file + # + my $stat = LICENSE_NONE; + $stat = &handleDistributionValue(\$filecontent, $full_filename); + if ($stat eq LICENSE_CHANGED) + { + $willModifiedFileCount++; + if ($modify) + { + close(FH); # Close null + writeFile(\$filecontent, $filename, $full_filename); + } + } + return; # All done + } + + elsif ( (lc($suffix) eq ".mk" ) or + (lc($suffix) eq ".pl") or (lc($suffix) eq ".py") or (lc($suffix) eq ".pm") or # script + (lc($suffix) eq ".pro") or (lc($suffix) eq ".pri") ) # Qt specific + { + # Makefile, Perl or Python script (# comment) + $cm = '#'; + $cm2 = '#'; + $newheader = &headerOf(OTHER_HEADER()); + $header_regexp = $header_regexps[COMMENT_PERL]; + } + elsif ((lc($suffix) eq ".bat" ) or (lc($suffix) eq ".cmd" )) + { + # Windows command script (@rem comment) + $cm = '@rem'; + $cm2 = '@rem'; + $newheader = &headerOf(OTHER_HEADER()); + $newheader =~ s/\#/\@rem/g; # use rem as comment start, not # + #$newheader = "\@echo off\n" . $newheader; # Disable std output, otherwise rem statements are shown + #$newheader = $newheader . "\@echo on\n"; # Enable std output + $header_regexp = $header_regexps[COMMENT_CMD]; + } + elsif (lc($suffix) eq ".pkg" or lc($suffix) eq ".asm") + { + # SIS package file or Assembly file (; comment) + $cm = ';'; + $cm2 = ';'; + $newheader = &headerOf(OTHER_HEADER()); + $newheader =~ s/\#/\;/g; # use ; as comment start + $header_regexp = $header_regexps[COMMENT_SIS_ASM]; + } + elsif (lc($suffix) eq ".s") + { + # Not all .s files are assemby files !!! + # + if ($filecontent =~ m/\#include\s*\"armasmdef\.h\"/s) + { + # ARM assembly file (C comment) + $newheader = &headerOf(CPP_HEADER()); + # Match both C and C++ comment syntaxes + $isCcomment = 1; + $header_regexp = $header_regexps[COMMENT_C]; + $header_regexp2 = $header_regexps[COMMENT_CPP]; # Use without /s in regexp eval ! + } + elsif ($filecontent =~ m/[\s\t]+AREA[\s\t]+/s) # AREA statement + { + # RVCT assembly file (; comment) + $cm = ';'; + $cm2 = ';'; + $newheader = &headerOf(OTHER_HEADER()); + $newheader =~ s/\#/\;/g; # use ; as comment start + $header_regexp = $header_regexps[COMMENT_SIS_ASM]; + } + else + { + # Not recognized + $unrecog = 1; + printLog(LOG_WARNING, "Assembly file content not recognized, ignored ". $full_filename . "\n"); + } + } + elsif (lc($suffix) eq ".script" ) + { + # Test scipt (// comment) + $cm = '//'; + $cm2 = '//'; + $newheader = &headerOf(OTHER_HEADER()); + $newheader =~ s/\#/\/\//g; # use // as comment start + $header_regexp = $header_regexps[COMMENT_SCRIPT]; + } + else + { + # C/C++ syntaxed file + $newheader = &headerOf(CPP_HEADER()); + # Match both C and C++ comment syntaxes + $isCcomment = 1; + $header_regexp = $header_regexps[COMMENT_C]; + $header_regexp2 = $header_regexps[COMMENT_CPP]; # Use without /s in regexp eval ! + } + + if ($unrecog) + { + close(FH); + $unrecogCount++; + return; + } + + ################### + # Pick up old header in the very first comment block. + # If the actual license text is in the later comment block, it may generate + # UNCLEAR COPYRIGHT CASE (See Consistency checks) + ################### + # + if ($header_regexp2) + { + if ($filecontent =~ m/$header_regexp2/) # Note /s not used by purpose ! + { + $oldheader = $&; + $oldheader2 = $&; + $matchPos2 = $-[0]; + $isCPPcomment = 1; + printLog(LOG_DEBUG, "Orig C++ header:$matchPos2=($oldheader)\n"); + } + } + + if ($filecontent =~ m/$header_regexp/s) + { + $oldheader = $&; + $matchPos1 = $-[0]; + $isCPPcomment = 0; + if ($oldheader2 && ($matchPos2 < $matchPos1)) + { + $oldheader = $oldheader2; # C++ header was earlier + $isCPPcomment = 1; # revert back + } + else + { + printLog(LOG_DEBUG, "Orig C or other header:$header_regexp,$matchPos1\n"); + printLog(LOG_DEBUG, "Orig C or other header:($oldheader)\n"); + } + } + + # + ################### + # Process old header + ################### + + # Handle -verify option + if ($optVerify) + { + if ($optLgpl) + { + &handleVerifyLgpl(\$filecontent, \$oldheader, $cm, $full_filename, $File::Find::dir); + } + elsif ($optEpl) + { + &handleVerifyEpl(\$filecontent, \$oldheader, $cm, $full_filename, $File::Find::dir); + } + else + { + &handleVerify(\$filecontent, \$oldheader, $cm, $full_filename, $File::Find::dir); + } + + close(FH); + return; # All done + } + + # + # Try switch license from SFL to EPL / S60-OEM release + # + my $switchStat = LICENSE_NONE; + $switchStat = &switchLicense(\$filecontent, \$oldheader, $cm, $full_filename, $isCPPcomment); + if ($switchStat eq LICENSE_CHANGED) + { + # OK the switch was sucessful + $willModifiedFileCount++; + if ($modify) + { + close(FH); + writeFile(\$filecontent, $filename, $full_filename); + } + close(FH); + return; # All done + } + elsif ($switchStat eq LICENSE_NOT_SUPPORTED) + { + close(FH); + return; # No worth continue + } + + + # Otherwise (error or no license) continue to create new header + + ################### + # Consistency checks + ################### + if ( (!($oldheader =~ m/$CopyrPattern/is)) && ($filecontent =~ m/$BeginLicenseBlockPattern/s)) + { + # Looks like header is something weired going on. First comment block contains no copyright, + # and still there is "BEGIN LICENSE" block in the file + $UnclearCopyrCount++; + printLog(LOG_INFO, "Non-Nokia copyright (#1) ". $full_filename . "\n"); + printResult(HEADER_CONTEXT() . "$sep"."Non-Nokia copyright $sep$sep"."BEGIN LICENSE BLOCK$sep$full_filename$sep$linenumtext\n"); + close(FH); + return; + } + + # + # Switch license from S60 to SFL/EPL according to options + # + + my $otherCopyr = 0; + my $noCopyr = 0; + my $ExternalToNokiaCopyr = 0; + my $s60header = 0; + + # First remove all "Nokia copyright" texts + weired "Copyright known-words" from header + # This because, the header can contain both Nokia and other company copyright statements + my $testheader = makeTestHeader(\$oldheader, 0, REMOVE_SYMBIAN); + printLog(LOG_DEBUG, "Cleaned header=($testheader)\n"); + + # Now test whether it contain non-Nokia (=Nokia+Symbian) copyright statements + # The rule is: If this hits, do not touch the header + + if ($testheader =~ m/$NonNokiaCopyrPattern/is) + { + # Some other than Nokia & Symbian copyright exist in header + $otherCopyr = 1; + $modify = 0; # !!! Do not modify file !!! + $willmodify = 0; + $otherCopyrCount++; + my $failReason = ""; + if (!checkPortionsCopyright(\$oldheader, 0, \$failReason)) + { + printLog(LOG_WARNING, "Non-Nokia copyright (#2) ". $full_filename . "\n"); + printResult(HEADER_CONTEXT() . "$sep"."Non-Nokia copyright$sep$sep$sep$full_filename$sep$linenumtext\n"); + } + else + { + printResult(HEADER_CONTEXT() . "$sep"."Non-Nokia (portions Nokia) copyright$sep$sep$sep$full_filename$sep$linenumtext\n"); + printLog(LOG_INFO, "Non-Nokia (portions Nokia) copyright ". $full_filename . "\n"); + } + + close(FH); + return; # Quit + } + + # Test header has Nokia or Symbian copyright statement or it could be some other comment + # Check the rest of file + my $wholefile = makeTestHeader(\$filecontent, 1, REMOVE_SYMBIAN); # Check the rest of file + if ($wholefile =~ m/$NonNokiaCopyrPattern/is) + { + # The header might be empty due to weired file header style. + # Check the whole file content, it could be non-nokia file? + $modify = 0; # !!! Do not modify file !!! + $willmodify = 0; + my $failReason = ""; + if (!checkPortionsCopyright(\$filecontent, 1, \$failReason)) + { + $UnclearCopyrCount++; + printLog(LOG_INFO, "Non-Nokia copyright (#2) ". $full_filename . "\n"); + printResult(HEADER_CONTEXT() . "$sep"."Non-Nokia copyright$failReason$sep$sep$sep$full_filename$sep$linenumtext\n"); + } + else + { + printResult(HEADER_CONTEXT() . "$sep"."Non-Nokia (portions Nokia) copyright$sep$sep$sep$full_filename$sep$linenumtext\n"); + printLog(LOG_INFO, "Non-Nokia (portions Nokia) copyright ". $full_filename . "\n"); + } + close(FH); + return; # Quit + } + + # Check if header is already OK. + # This is needed to keep Ex-Symbian C++ comment syntaxes. + # Also, this avoid unncessary changes in headers. + # NOTE ! If header need to be converted to a new format the check must return FALSE !!! + my $license = licenceIdForOption(); + if (checkHeader(\$filecontent, \$oldheader, $cm, $full_filename, $File::Find::dir, \$license)) + { + if (!checkNoMultipleLicenses(\$filecontent, \$oldheader, $cm, $full_filename, $File::Find::dir)) + { + printResult(HEADER_CONTEXT() . "$sep"."Multiple licenses$sep$sep$sep$full_filename$sep" ."1\n"); + printLog(LOG_ERROR, "Multiple licenses:". $full_filename . "\n"); + close(FH); + return; # Failed + } + else + { + # Quit here, header OK + printLog(LOG_INFO, "Header already OK ($license): $full_filename\n"); + close(FH); + return; # Quit + } + } + + # Check if Ex-Symbian file + my $testheader = makeTestHeader(\$oldheader, 0, KEEP_SYMBIAN); + if ($testheader =~ m/$ExternalToNokiaCopyrPattern/is) + { + # External copyright moved to Nokia + my $txt = $1; + $txt =~ s/,//; + $ExternalToNokiaCopyr = 1; + $ExternalToNokiaCopyrCount++; + if ($isCPPcomment) + { + # Normalize the C++ header syntax back to C comment + $modifiedFilecontent = &normalizeCppComment($header_regexp2,$filecontent, \$oldheader); + printLog(LOG_DEBUG, "Normalized External header=($oldheader)\n"); + } + if ($testheader =~ /$copyrYearPattern/) + { + if ($& =~ /$copyrYearPattern2/) + { + $oldCopyrightYear = $&; + } + printLog(LOG_DEBUG, "Old copyright=($oldCopyrightYear)\n"); + } + printLog(LOG_INFO, "Copyright will be converted to Nokia: $full_filename\n"); + printResult(HEADER_CONTEXT() . "$sep"."Converted copyright$sep$sep$sep$full_filename$sep$linenumtext\n"); + } + + elsif ($oldheader =~ m/$NokiaCopyrPattern/is) + { + # Consider it to be Nokia copyright + $s60header = 1; + $NokiaCopyrCount++; + printLog(LOG_DEBUG, "Nokia header=($full_filename)\n"); + if ($isCPPcomment) + { + # Normalize the C++ header syntax back to C comment + $modifiedFilecontent = &normalizeCppComment($header_regexp2, $filecontent, \$oldheader); + printLog(LOG_DEBUG, "Normalized Nokia header=($oldheader)\n"); + } + if ($oldheader =~ /$copyrYearPattern/) + { + if ($& =~ /$copyrYearPattern2/) + { + $oldCopyrightYear = $&; + } + printLog(LOG_DEBUG, "Old copyright2=($oldCopyrightYear)\n"); + } + } + elsif (! ($testheader =~ m/$CopyrPattern/is) ) + { + # No copyright in the header. + $NoCopyrCount++; + $noCopyr = 1; + # printResult(HEADER_CONTEXT() . "$sep"."No Copyright$sep$sep$sep$full_filename$sep$linenumtext\n"); + } + else + { + $UnclearCopyrCount++; + $modify = 0; # !!! Do not modify file !!! + $willmodify = 0; + printLog(LOG_ERROR, "UNCLEAR copyright ". $full_filename . "\n"); + printResult(HEADER_CONTEXT() . "$sep"."UNCLEAR COPYRIGHT CASE$sep$sep$sep$full_filename$sep$linenumtext\n"); + } + + + # Get description from current header + if ($oldheader =~ m/$cm\s*Description\s*\:(.*?)$cm\s*(Version)/s) + { + # Description followed by Version + $description = $1; + printLog(LOG_DEBUG, "Old description followed by version ($description)\n"); + } else + { + # Description without Version + # ORIG if ($oldheader =~ m/$cm?\s*Description\s*\:(.*?)$cm\s*(\n)/s) + if ($oldheader =~ m/$cm?\s*Description\s*\:(.*?)($cm|$cm\/|\n)\s*\n/s) + { + $description = $1; + printLog(LOG_DEBUG, "Old description not followed by version ($description)\n"); + } + } + + if ($isCcomment) + { + $description =~ s/\/\*.*//; # Remove possible /* + $description =~ s/\*\/.*//; # Remove possible */ + $description =~ s/\=//g; # Remove possible =/ + } + + # Get contributors from old header + if ( $oldheader =~ m/$cm\s*Contributors\s*\:(.*?)$cm\s*Description\s*\:/s) + { + $contributors = $1; + printLog(LOG_DEBUG, "Old contributors ($contributors)\n"); + } + + # Keep description text + if($description) + { + $newheader =~ s/Description:[ \t]*\n/Description: $description/s; + } + + + #Keep contributor list + if ($contributors) + { + $newheader =~ s/$cm[ \t]*Contributors:[ \t]*\n$cm[ \t]*\n/$cm2 Contributors:$contributors/s; + } + + + ################### + # Modify the header + ################### + if($oldheader) + { + { + # Update the old header to new one + # Old header may be just a description comment, e.g. in script + # + + if ($otherCopyr) + { + # Other copyright statement, do not touch ! + printLog(LOG_DEBUG, "Non-Nokia file not modified: $full_filename\n"); + } + elsif ($noCopyr) + { + + # No copyright statement + if (!isGeneratedHeader(\$oldheader)) + { + # Just add new header + $filecontent = $newheader . $filecontent; + printLog(LOG_INFO, "New header will be added: $full_filename\n"); + } + else + { + printLog(LOG_INFO, "Generated file ignored: $full_filename\n"); + } + } + else + { + # Replace the old external / S60 header + my $newHeaderCopyrYear; + if ($newheader =~ /$copyrYearPattern2/) + { + # This is picked up from newheader template in this script, so should work always ! + $newHeaderCopyrYear = $&; # Pick up year from new header + printLog(LOG_DEBUG, "Template header copyright=($newHeaderCopyrYear)\n"); + } + if (!$newHeaderCopyrYear) + { + # Anyway, some weired error happended + $newHeaderCopyrYear = DEFCOPYRIGHTYEAR; + } + + # Create new copyright years + if ($oldCopyrightYear && !($oldCopyrightYear =~ /$newHeaderCopyrYear/)) + { + # Keep the old copyright !!! + # !!! If adding new copyright year to old header, uncomment the next line !!! + # $oldCopyrightYear .= ",$newHeaderCopyrYear"; + } + if (!$oldCopyrightYear) + { + # Nothing found + $oldCopyrightYear = $newHeaderCopyrYear; + } + printLog(LOG_DEBUG, "New header copyright:$oldCopyrightYear\n"); + $newheader =~ s/$newHeaderCopyrYear/$oldCopyrightYear/; + printLog(LOG_DEBUG, "New header:$full_filename,($newheader)\n"); + if ($modifiedFilecontent) + { + $filecontent = $modifiedFilecontent; # Use the already modified content as basis + } + + # + # SET THE NEW HEADER + # + if (!($filecontent =~ s/$header_regexp/$newheader/s)) + { + printLog(LOG_ERROR, "FAILED to change file header: ". $full_filename . "\n"); + $LicenseChangeErrors++; + $modify = 0; # Can not modify on failure + $willmodify = 0; + } + else + { + printLog(LOG_INFO, "File header will be changed: $full_filename\n"); + } + } + } + } + else + { + if (!isGeneratedHeader(\$filecontent)) # Ensure file is not generated + { + # Missing old header, add new header as such + printLog(LOG_INFO, "Missing header will be added: $full_filename\n"); + $filecontent = $newheader."\n".$filecontent; + } + else + { + printLog(LOG_INFO, "Generated file ignored: $full_filename\n"); + } + } + + if ($description =~ m/^\s*$/g || $description =~ m/$descrTemplateOnly/) + { + $noDescrcount++; + if ($optDescription) + { + printResult(HEADER_CONTEXT() . "$sep"."Description missing$sep$sep$sep$full_filename$sep$linenumtext\n"); + } + } + + close(FH); + + if ($modify) + { + # Re-open the file for modifications + chmod 0777, $filename if !-w; # remove first R/O + open(FH, "+<$filename") or return printLog(LOG_ERROR, "Failed to open file for modifying: $full_filename\n"); + print FH $filecontent or printLog(LOG_ERROR, "Failed to modify file: $full_filename\n"); + truncate(FH, tell(FH)); + $modifiedFileCount++; + close(FH); + } + + if ($willmodify) + { + # Only for statistics + $willModifiedFileCount++; + } + +} + + + +################################## +# Callback for the find function +# (postprocess) +# Note ! "no_chdir" not used +################################## +sub postprocess +{ + my $dir = $File::Find::dir; + printLog(LOG_DEBUG, "postprocess $dir\n"); + + return if (-e DISTRIBUTION_FILENAME); # Already exists ? + + my $full_filename = $dir . "/" . DISTRIBUTION_FILENAME; # Full name needed for results and log + my $filename = DISTRIBUTION_FILENAME; + + if ($ignorefilepattern && $full_filename =~ m/$ignorefilepattern/i) + { + printLog(LOG_DEBUG, "Missing file ignored by pattern: ". $full_filename . "\n"); + $ignoreCount++; + return; + } + + my $filecontent = ""; + my $stat = LICENSE_NONE; + $stat = &handleDistributionValue(\$filecontent, $full_filename); + if ($stat eq LICENSE_CHANGED && $optCreate && isDirectoryNonEmpty('.')) + { + # Create new distribution file to non-empty directory + printResult(DISTRIBUTION_CONTEXT() . "$sep"."New file$sep$sep$sep$full_filename$sep$linenumtext\n"); + if ($optModify) + { + # Without -modify it is possible to see what new files will created + createAndWriteFile(\$filecontent, $filename, $full_filename); + } + $createCount++; # For statistics + } + + +} + + +################################## +# Callback for the find function +# (preprocess). Used by option -verify +# Note ! "no_chdir" not used +################################## +sub preprocess +{ + my $dir = $File::Find::dir; + printLog(LOG_DEBUG, "preprocess $dir\n"); + $lastDistributionValue = ""; # Empty first + + if (!isDirectoryNonEmpty('.')) + { + # Ignore empty dirs + return @_; # Return input args + } + if (!$optVerify) + { + return @_; # Return input args + } + + # + # Currently option -verify required !!! + # + + my $full_filename = $dir . "/" . DISTRIBUTION_FILENAME; # Full name needed for results and log + $full_filename =~ s/\\/\//g; # Standardize name + + my $filename = DISTRIBUTION_FILENAME; + if ($ignorefilepattern && $full_filename =~ m/$ignorefilepattern/i) + { + if (! ($dir =~ m/$INTERNAL/i) ) + { + return @_; + } + } + + # Check existency of the file + if (!open(FH, "<$filename")) + { + printResult(DISTRIBUTION_CONTEXT() . "$sep"."Distribution policy file missing$sep$sep$sep$full_filename$sep$linenumtext\n"); + $verifyFailedCount[VERI_MISSING_FILE]++; + return @_; # Return input args + } + + my $content = ; # IF CONTENT CHECKS + close FH; + + $content =~ s/\n//g; # Remove all new-lines + $content =~ s/^\s+//g; # trim left + $content =~ s/\s+$//g; # trim right + $lastDistributionValue = $content; # Save to global variable for the sub handleVerify + + printLog(LOG_DEBUG, "$full_filename content=$content\n"); + + if ($dir =~ m/$INTERNAL/i) + { + if ( ($content eq SFL_DISTRIBUTION_VALUE) || ($content eq EPL_DISTRIBUTION_VALUE) ) + { + # Internal directory has SFL or EPL distribution value, something is wrong ! + my $comment = ""; # Leave it just empty + printResult(DISTRIBUTION_CONTEXT() . "$sep"."Internal directory going to SF (current value $content)$sep$comment$sep$sep$full_filename$sep$linenumtext\n"); + $verifyFailedCount[VERI_INTERNAL_TO_SF]++; + } + } + elsif (! (($content eq SFL_DISTRIBUTION_VALUE) || ($content eq EPL_DISTRIBUTION_VALUE))) + { + # Neither SFL nor EPL value + my $comment = getCommentText($content,0,"0,3,7,950", $full_filename); + my $isSFId = &isSFDistribution($content); + if (!$isSFId) + { + printResult(DISTRIBUTION_CONTEXT() . "$sep"."SFL or EPL value missing (current value $content)$sep$comment$sep$sep$full_filename$sep$linenumtext\n"); + $verifyFailedCount[VERI_MISSING_ID]++; + } + } + + return @_; # Return input args + +} + +################################################## +# Read distribution file from given directory +################################################## +sub readDistributionValue +{ + + my $dir = shift; + + my $filename = DISTRIBUTION_FILENAME; + my $content = ""; + + if (open(FH, "<$filename")) + { + $content = ; + close FH; + } + + $content =~ s/\n//g; # Remove all new-lines + $content =~ s/^\s+//g; # trim left + $content =~ s/\s+$//g; # trim right + + return $content; +} + + +################################################## +# Make test header from given input text +################################################## +sub makeTestHeader +{ + my $ref = shift; # Input text reference + my $isWholeFile = shift; # $ref is the file content + my $removeExternalToNokia = shift; # Remove to Nokia transferreable copyright texts + + my $tstheader = ""; + + if (!$isWholeFile) + { + $tstheader = $$ref; + } + else + { + # To optimize, whole file == 10k !!! + # The proper header should be included in that amount of data. + $tstheader = substr($$ref, 0, 10*1024); + } + $tstheader =~ s/$NokiaCopyrPattern//gi; + $tstheader =~ s/$PortionsNokiaCopyrPattern//gi; + $tstheader =~ s/$RemoveS60TextBlockPattern//si; + if ($removeExternalToNokia) + { + $tstheader =~ s/$ExternalToNokiaCopyrPattern//gi; + $tstheader =~ s/$PortionsSymbianCopyrPattern//gi; + } + + # Take out special texts containing copyright word + $tstheader =~ s/Copyright\s*\(c\)\s*\.//gi; + $tstheader =~ s/COPYRIGHT[\s\n\*\#+;]*(HOLDER|OWNER|notice)//gi; + + return $tstheader; +} + + +################################################## +# Check whether portions copyright is OK +# Call this for non Nokia cases only ! +################################################## +sub checkPortionsCopyright +{ + my $ref = shift; # Input text reference + my $isWholeFile = shift; # $ref is the file content + my $failReason_ref = shift; # check failure reason (OUT) + + my $tstheader = ""; + + if (!$isWholeFile) + { + $tstheader = $$ref; + } + else + { + # The portions info should be included within first 10 Kb of file + $tstheader = substr($$ref, 0, 10*1024); + } + + if ($tstheader =~ m/$PortionsSymbianCopyrPattern/is) + { + # Symbian portions copyright should be converted to Nokia one + if (!($tstheader =~ m/$PortionsNokiaCopyrPattern/is)) + { + $$failReason_ref = "(portions Symbian copyright)"; + } + else + { + $$failReason_ref = "(portions Nokia+Symbian copyright)"; + } + return 0; + } + + if (!($tstheader =~ m/$NewPortionsNokiaCopyrPattern/is)) + { + # No portions copyright present + $$failReason_ref = ""; + return 0; + } + + return 1; # Should be OK +} + + +################################################## +# Get comment text by ID or filename +# Returns currently empty or value of the $IGNORE +################################################## +sub getCommentText +{ + my $distributionValue = shift; + my $contains = shift; + my $pattern = shift; + my $fullfilename = shift; + + if ($contains) + { + if ($pattern =~ m/$distributionValue/) + { + return $IGNORE; + } + } + else + { + # Not contains + if (!($pattern =~ m/$distributionValue/)) + { + return $IGNORE; + } + } + + my $ignoreThis = $manualIgnoreFileHash{lc($fullfilename)}; + if (defined $ignoreThis) + { + printLog(LOG_DEBUG, "$IGNORE_MAN 2: $fullfilename\n"); + return $IGNORE_MAN; + } + + return ""; +} + + +################################################## +# Write content to file +################################################## +sub writeFile +{ + my $filecontent_ref = shift; + my $filename = shift; + my $full_filename = shift; + + my $fh; + + chmod 0777, $filename if !-w; # remove first R/O + open($fh, "+<$filename") or return printLog(LOG_ERROR, "Failed to open file for modifying: $full_filename\n"); + print $fh $$filecontent_ref or printLog(LOG_ERROR, "Failed to modify file: $full_filename\n"); + truncate($fh, tell($fh)); + close($fh); + + $modifiedFileCount++; +} + +################################################## +# Create file and write content to file +################################################## +sub createAndWriteFile +{ + my $filecontent_ref = shift; + my $filename = shift; + my $full_filename = shift; + + my $fh; + + open($fh, ">$filename") or return printLog(LOG_ERROR, "Failed to create file: $full_filename\n"); + print $fh $$filecontent_ref or printLog(LOG_ERROR, "Failed to write file: $full_filename\n"); + close($fh); +} + +################################## +# Check if current directory is empty +################################## +sub isDirectoryNonEmpty +{ + my ($dir) = @_; + opendir (DIR,$dir) or printLog(LOG_ERROR, "Can't opendir $dir\n"); + for(readdir DIR) + { + if (-f $_) + { + closedir DIR; + return 1; + }; + } + closedir DIR; + return 0; +} + + +################################################## +# Change SFL back to S60, or +# Change SFl to EPL +# Returns LICENSE_CHANGED if function switched the license succesfully +################################################## +# Switch only license text and URL +my $sflText = '"Symbian Foundation License v1.0"'; +my $sflTextPattern = '(the\s*License\s*)?\"Symbian\s*Foundation\s*License\s*v1\.0\"'; +my $sflUrlPattern = 'http\:\/\/www\.symbianfoundation\.org\/legal\/sfl\-v10\.html'; +my $sflUrl = 'http://www.symbianfoundation.org/legal/sf'.'l-v10.html'; +my $eplText = '"Eclipse Public License v1.0"'; +my $eplUrl = 'http://www.eclipse.org/legal/epl-v10.html'; +my $eplUrlPattern = 'http\:\/\/www\.eclipse\.org\/legal\/epl\-v10\.html'; +my $eplTextPattern = '"Eclipse\s*Public\s*License\s*v1\.0"'; +my $oldEplTextPattern = 'the\s*License\s*"Eclipse\s*Public\s*License\s*v1\.0'; # "the License" is unncessary + +sub switchLicense +{ + my $filecontent_ref = shift; + my $header_ref = shift; + my $commentChar = shift; + my $fullfilename = shift; + my $isCPPcomment = shift; + + my $testValueSfl = ""; + my $testValueEpl = ""; + my $testValueS60 = ""; + + if ($isCPPcomment) + { + # xSymbian files use this style + $commentChar = '//'; + # in xSymbian files there are comments like, /// some text + $$filecontent_ref =~ s/(\/){3,}/\/\//g; # replace ///+ back to // + } + + + # In from value \* need to be escaped. + my $FromSFLText = &partialHeaderOf(SFL_LICENSE,$commentChar, \$testValueSfl); + my $FromEPLText = &partialHeaderOf(EPL_LICENSE,$commentChar, \$testValueEpl); + + $commentChar =~ s/\\//; # Remove \ from possible \* + my $ToS60Text = &partialHeaderOf(S60_LICENSE,$commentChar, \$testValueS60); + + # Note that partial headers are manually quoted in the declaration + # Otherwise \Q$SFLText\E and \Q$EPLText\E would be needed around those ones + # because plain text contains special chars, like . + + printLog(LOG_DEBUG, "switchLicense: $fullfilename, $testValueEpl\n"); + + if ($$filecontent_ref =~ m/$testValueSfl/s) + { + # SFL license + + if ($optOem) + { + # Switch from SFL to S60 + if (!($$filecontent_ref =~ s/$FromSFLText/$ToS60Text/s)) + { + printLog(LOG_ERROR, "FAILED to change SFL license to S60: ". $fullfilename . "\n"); + $LicenseChangeErrors++; + return LICENSE_ERROR; + } + printLog(LOG_WARNING, "License will be swicthed from SFL to S60: ". $fullfilename . "\n"); + $SflToS60Changes++; + return LICENSE_CHANGED; + } + elsif ($optEpl) + { + # Switch from SFL to EPL + if (! ( ($$filecontent_ref =~ s/$sflTextPattern/$eplText/s) && ($$filecontent_ref =~ s/$sflUrlPattern/$eplUrl/s) ) ) + { + printLog(LOG_ERROR, "FAILED to change SFL to EPL: ". $fullfilename . "\n"); + $LicenseChangeErrors++; + return LICENSE_ERROR; + } + else + { + printLog(LOG_INFO, "License will be switched from SFL to EPL: ". $fullfilename . "\n"); + } + $SflToEplChanges++; + return LICENSE_CHANGED; + } + } + + if ($$filecontent_ref =~ m/$testValueEpl/s) + { + if ($optOem) + { + printLog(LOG_ERROR, "Not supported to change EPL to S60: ". $fullfilename . "\n"); + return LICENSE_NOT_SUPPORTED; + } + elsif (!$optEpl) + { + # Switch from EPL to SFL + if (! ( ($$filecontent_ref =~ s/$eplTextPattern/$sflText/s) && ($$filecontent_ref =~ s/$eplUrlPattern/$sflUrl/s) ) ) + { + printLog(LOG_ERROR, "FAILED to change EPL to SFL: ". $fullfilename . "\n"); + $LicenseChangeErrors++; + return LICENSE_ERROR; + } + else + { + printLog(LOG_WARNING, "License will be switched from EPL to SFL: ". $fullfilename . "\n"); + } + $EplToSflChanges++; + return LICENSE_CHANGED; + } + + # EPL text cleanup (remove unncessary "the License") + if ($$filecontent_ref =~ m/$oldEplTextPattern/s) + { + # EPL header contains extra words, get rid of them (allow script replace old header) + if ($$filecontent_ref =~ s/$oldEplTextPattern/$eplText/s) + { + # Not error if fails + printLog(LOG_INFO, "Unnecessary \"the License\" will be removed: $fullfilename\n"); + return LICENSE_CHANGED; + } + } + + } + else + { + return LICENSE_NONE; # Allow caller decide + } + +} + +################################################## +# Verify changes +################################################## +sub handleVerify +{ + my $filecontent_ref = shift; + my $header_ref = shift; + my $commentChar = shift; + my $fullfilename = shift; + my $directory = shift; + + my $testValueSfl = ""; + my $testValueEpl = ""; + my $FromSFLText = &partialHeaderOf(SFL_LICENSE,$commentChar, \$testValueSfl); + my $FromEPLText = &partialHeaderOf(EPL_LICENSE,$commentChar, \$testValueEpl); + + if ($lastDistributionValue eq "") + { + # Distribution file may be empty if giving single file as input + # Read it + $lastDistributionValue = readDistributionValue($directory); + } + + printLog(LOG_DEBUG, "handleVerify $fullfilename, $$header_ref\n"); + + # First check Non-Nokia copyright files + my $testheader = makeTestHeader($header_ref, 0, REMOVE_SYMBIAN); + if (($testheader =~ m/$NonNokiaCopyrPattern/is)) + { + printLog(LOG_DEBUG, "DEBUG:Extra check1 $&\n"); + if (!($testheader =~ m/$ExternalToNokiaCopyrPattern/si)) + { + # Non-nokia file + if ($testheader =~ m/$copyrYearPattern/si) + { + # Looks like copyright statement + printResult(HEADER_CONTEXT() . "$sep"."Non-Nokia copyright$sep" . "$IGNORE$sep$lastDistributionValue$sep$fullfilename$sep$linenumtext\n"); + $otherCopyrCount++; + $verifyFailedCount[VERI_OK_NON_NOKIA]++; + return 1; # OK + } + else + { + # Incomplete copyright ? + printResult(HEADER_CONTEXT() . "$sep"."Non-Nokia incomplete copyright$sep" . "$IGNORE?$sep$lastDistributionValue$sep$fullfilename$sep$linenumtext\n"); + $verifyFailedCount[VERI_OK]++; + return 0; + } + } + } + + # The header might be empty due to weired file header style. + # Check the whole file content, it could be non-nokia file? + my $filestart = makeTestHeader($filecontent_ref, 1, REMOVE_SYMBIAN); + + if ($filestart =~ m/$NonNokiaCopyrPattern/is) + { + # There is Non-Nokia copyright statement in the file + if (($filestart =~ m/$testValueSfl/is) || ($filestart =~ m/$testValueEpl/is)) + { + # Non-Nokia file, but still SFL or EPL header + printResult(HEADER_CONTEXT() . "$sep"."UNCLEAR Non-Nokia copyright with SFL/EPL$sep" . "$sep$lastDistributionValue$sep$fullfilename$sep$linenumtext\n"); + $verifyFailedCount[VERI_UNCLEAR_COPYR]++; + return 0; + } + elsif ($$filecontent_ref =~ m/$OldNokiaPattern2/is) + { + printResult(HEADER_CONTEXT() . "$sep"."UNCLEAR Old Nokia copyright$sep" . "$sep$lastDistributionValue$sep$fullfilename$sep$linenumtext\n"); + $verifyFailedCount[VERI_OLD_NOKIA_HEADER]++; + return 0; + } + else + { + # Non-Nokia file + my $failReason = ""; + if (!checkPortionsCopyright($filecontent_ref, 1, \$failReason)) + { + printResult(HEADER_CONTEXT() . "$sep"."UNCLEAR Non-Nokia copyright$failReason$sep" . "$sep$lastDistributionValue$sep$fullfilename$sep$linenumtext\n"); + $verifyFailedCount[VERI_UNCLEAR_COPYR]++; + return 0; + } + else + { + # Contains portions copyright + printResult(HEADER_CONTEXT() . "$sep"."Non-Nokia (portions Nokia) copyright$sep" . "$IGNORE$sep$lastDistributionValue$sep$fullfilename$sep$linenumtext\n"); + return 1; + } + } + } + + # + # OK, it should be Nokia copyrighted file + # + + # Note that partial headers are manually quoted in the declaration + # Otherwise \Q$SFLText\E and \Q$EPLText\E would be needed around those ones + # because plain text contains special chars, like . + printLog(LOG_DEBUG, "handleVerify testheaders: $testValueSfl,$testValueEpl,$$header_ref\n"); + + if ( !( ($$header_ref =~ m/$testValueSfl/s) || ($$header_ref =~ m/$testValueEpl/s) || + ($$filecontent_ref =~ m/$testValueSfl/s) || ($$filecontent_ref =~ m/$testValueEpl/s) + ) ) + { + # Header not found from header or whole file + if (isGeneratedHeader($header_ref) || isGeneratedHeader($filecontent_ref)) + { + # OK, it is generated header + if ($optOutputOK) + { + printResult(HEADER_CONTEXT() . "$sep"."OK$sep" . "Generated header$sep$lastDistributionValue$sep$fullfilename$sep$linenumtext\n"); + } + $verifyFailedCount[VERI_OK]++; + return 1; # OK + } + + my $comment = getCommentText($lastDistributionValue, 0, "0,3,7", $fullfilename); + if (($$header_ref =~ m/$OldNokiaPattern2/is) || ($$filecontent_ref =~ m/$OldNokiaPattern2/is)) + { + printResult(HEADER_CONTEXT() . "$sep"."SFL or EPL header missing (old Nokia copyright)$sep$comment$sep$lastDistributionValue$sep$fullfilename$sep$linenumtext\n"); + } + else + { + printResult(HEADER_CONTEXT() . "$sep"."SFL or EPL header missing$sep$comment$sep$lastDistributionValue$sep$fullfilename$sep$linenumtext\n"); + } + $verifyFailedCount[VERI_MISSING_HEADER]++; + return 0; + } + + # Cross header versus distribution ID + if ($lastDistributionValue ne "") + { + # Also other than 3 or 7 may be OK based on the config file + my $isSFId = &isSFDistribution($lastDistributionValue); + printLog(LOG_DEBUG, "DEBUG:handleVerify:Other ID OK=$isSFId\n"); + if ( (($$header_ref =~ m/$testValueSfl/s) || ($$filecontent_ref =~ m/$testValueSfl/s)) && !$isSFId) + { + my $comment = getCommentText($lastDistributionValue, 0, "0,3,7", $fullfilename); + printResult(HEADER_CONTEXT() . "$sep"."SFL header vs. distribution id ($lastDistributionValue) mismatch$sep$comment$sep$lastDistributionValue$sep$fullfilename$sep$linenumtext\n"); + $verifyFailedCount[VERI_ID_HEADER_MISMATCH]++; + return 0; + } + if ( (($$header_ref =~ m/$testValueEpl/s) || ($$filecontent_ref =~ m/$testValueEpl/s)) && !$isSFId ) + { + my $comment = getCommentText($lastDistributionValue, 0, "0,3,7", $fullfilename); + printResult(HEADER_CONTEXT() . "$sep"."EPL header vs. distribution id ($lastDistributionValue) mismatch$sep$comment$sep$lastDistributionValue$sep$fullfilename$sep$linenumtext\n"); + $verifyFailedCount[VERI_ID_HEADER_MISMATCH]++; + return 0; + } + } + + if (!checkNoMultipleLicenses($filecontent_ref, $header_ref, $commentChar, $fullfilename, $directory)) + { + printResult(HEADER_CONTEXT() . "$sep"."Multiple licenses$sep$sep$sep$fullfilename$sep" ."1\n"); + printLog(LOG_ERROR, "Multiple licenses:". $fullfilename . "\n"); + $verifyFailedCount[VERI_MULTIPLE_LICENSES]++; + return 0; # Failed + } + + + # We should have proper header in place + + printLog(LOG_DEBUG, "handleVerify: $$filecontent_ref\n"); + # Check New Nokia copyright pattern (added one sentence to the old one) + if (! (($$header_ref =~ m/$NewNokiaPattern/s) || ($$filecontent_ref =~ m/$NewNokiaPattern/s)) ) + { + my $comment = getCommentText($lastDistributionValue, 0, "0,3,7,950", $fullfilename); + printResult(HEADER_CONTEXT() . "$sep"."Proper Nokia copyright statement missing$sep$comment$sep$lastDistributionValue$sep$fullfilename$sep$linenumtext\n"); + $verifyFailedCount[VERI_PROPER_COPYRIGHT]++; + return 0; # Failed + } + + if ($optOutputOK) + { + printResult(HEADER_CONTEXT() . "$sep"."OK$sep" . "OK$sep$lastDistributionValue$sep$fullfilename$sep$linenumtext\n"); + } + $verifyFailedCount[VERI_OK]++; + + return 1; + +} + + +################################################## +# Verify changes +################################################## +sub handleVerifyEpl +{ + my $filecontent_ref = shift; + my $header_ref = shift; + my $commentChar = shift; + my $fullfilename = shift; + my $directory = shift; + + my $testValueSfl = ""; + my $testValueEpl = ""; + my $FromSFLText = &partialHeaderOf(SFL_LICENSE,$commentChar, \$testValueSfl); + my $FromEPLText = &partialHeaderOf(EPL_LICENSE,$commentChar, \$testValueEpl); + + if ($lastDistributionValue eq "") + { + # Distribution file may be empty if giving single file as input + # Read it + $lastDistributionValue = readDistributionValue($directory); + } + + printLog(LOG_DEBUG, "handleVerifyEpl $fullfilename, $$header_ref\n"); + + # First check Non-Nokia copyright files + my $testheader = makeTestHeader($header_ref, 0, REMOVE_SYMBIAN); + if (($testheader =~ m/$NonNokiaCopyrPattern/is)) + { + printLog(LOG_DEBUG, "DEBUG:Extra check1 $&\n"); + if (!($testheader =~ m/$ExternalToNokiaCopyrPattern/si)) + { + # Non-nokia file + if ($testheader =~ m/$copyrYearPattern/si) + { + # Looks like copyright statement + printResult(HEADER_CONTEXT() . "$sep"."Non-Nokia copyright$sep" . "$IGNORE$sep$lastDistributionValue$sep$fullfilename$sep$linenumtext\n"); + $otherCopyrCount++; + $verifyFailedCount[VERI_OK_NON_NOKIA]++; + return 1; # OK + } + else + { + # Incomplete copyright ? + printResult(HEADER_CONTEXT() . "$sep"."Non-Nokia incomplete copyright$sep" . "$IGNORE?$sep$lastDistributionValue$sep$fullfilename$sep$linenumtext\n"); + $verifyFailedCount[VERI_OK]++; + return 0; + } + } + } + + # The header might be empty due to weired file header style. + # Check the whole file content, it could be non-nokia file? + my $filestart = makeTestHeader($filecontent_ref, 1, REMOVE_SYMBIAN); + + if ($filestart =~ m/$NonNokiaCopyrPattern/is) + { + # There is Non-Nokia copyright statement in the file + if ($filestart =~ m/$testValueEpl/is) + { + # Non-Nokia file, but still EPL header + printResult(HEADER_CONTEXT() . "$sep"."UNCLEAR Non-Nokia copyright with EPL$sep" . "$sep$lastDistributionValue$sep$fullfilename$sep$linenumtext\n"); + $verifyFailedCount[VERI_UNCLEAR_COPYR]++; + return 0; + } + elsif ($$filecontent_ref =~ m/$OldNokiaPattern2/is) + { + printResult(HEADER_CONTEXT() . "$sep"."UNCLEAR Old Nokia copyright$sep" . "$sep$lastDistributionValue$sep$fullfilename$sep$linenumtext\n"); + $verifyFailedCount[VERI_OLD_NOKIA_HEADER]++; + return 0; + } + else + { + # Non-Nokia file + my $failReason = ""; + if (!checkPortionsCopyright($filecontent_ref, 1, \$failReason)) + { + printResult(HEADER_CONTEXT() . "$sep"."UNCLEAR Non-Nokia copyright$failReason$sep" . "$sep$lastDistributionValue$sep$fullfilename$sep$linenumtext\n"); + $verifyFailedCount[VERI_UNCLEAR_COPYR]++; + return 0; + } + else + { + # Contains portions copyright + printResult(HEADER_CONTEXT() . "$sep"."Non-Nokia (portions Nokia) copyright$sep" . "$IGNORE$sep$lastDistributionValue$sep$fullfilename$sep$linenumtext\n"); + return 1; + } + } + } + + # + # OK, it should be Nokia copyrighted file + # + + # Note that partial headers are manually quoted in the declaration + # Otherwise \Q$EPLText\E would be needed around those ones + # because plain text contains special chars, like . + printLog(LOG_DEBUG, "handleVerify testheaders: $testValueEpl,$$header_ref\n"); + + if ( !( ($$header_ref =~ m/$testValueEpl/s) || ($$filecontent_ref =~ m/$testValueEpl/s) ) ) + { + # Header not found from header or whole file + if (isGeneratedHeader($header_ref) || isGeneratedHeader($filecontent_ref)) + { + # OK, it is generated header + if ($optOutputOK) + { + printResult(HEADER_CONTEXT() . "$sep"."OK$sep" . "Generated header$sep$lastDistributionValue$sep$fullfilename$sep$linenumtext\n"); + } + $verifyFailedCount[VERI_OK]++; + return 1; # OK + } + + if (($$header_ref =~ m/$testValueSfl/s) || ($$filecontent_ref =~ m/$testValueSfl/s)) + { + # Still SFL header in place + printResult(HEADER_CONTEXT() . "$sep"."EPL header missing (SFL header used)$sep$sep$lastDistributionValue$sep$fullfilename$sep$linenumtext\n"); + } + elsif (($$header_ref =~ m/$OldNokiaPattern2/is) || ($$filecontent_ref =~ m/$OldNokiaPattern2/is)) + { + printResult(HEADER_CONTEXT() . "$sep"."EPL header missing (old Nokia copyright)$sep$sep$lastDistributionValue$sep$fullfilename$sep$linenumtext\n"); + } + else + { + printResult(HEADER_CONTEXT() . "$sep"."EPL header missing$sep$sep$lastDistributionValue$sep$fullfilename$sep$linenumtext\n"); + } + $verifyFailedCount[VERI_MISSING_HEADER]++; + return 0; + } + + # Cross header versus distribution ID + if ($lastDistributionValue ne "") + { + # Also other than 7 may be OK based on the config file + my $isSFId = &isSFDistribution($lastDistributionValue); + printLog(LOG_DEBUG, "DEBUG:handleVerify:Other ID OK=$isSFId\n"); + if ( ($$filecontent_ref =~ m/$testValueEpl/s) && ($lastDistributionValue ne EPL_DISTRIBUTION_VALUE) ) + { + printResult(HEADER_CONTEXT() . "$sep"."EPL header vs. distribution id ($lastDistributionValue) mismatch$sep$sep$lastDistributionValue$sep$fullfilename$sep$linenumtext\n"); + $verifyFailedCount[VERI_ID_HEADER_MISMATCH]++; + return 0; + } + } + + if (!checkNoMultipleLicenses($filecontent_ref, $header_ref, $commentChar, $fullfilename, $directory)) + { + printResult(HEADER_CONTEXT() . "$sep"."Multiple licenses$sep$sep$sep$fullfilename$sep" ."1\n"); + printLog(LOG_ERROR, "Multiple licenses:". $fullfilename . "\n"); + $verifyFailedCount[VERI_MULTIPLE_LICENSES]++; + return 0; # Failed + } + + + # We should have proper header in place + + printLog(LOG_DEBUG, "handleVerify: $$filecontent_ref\n"); + # Check New Nokia copyright pattern (added one sentence to the old one) + if (! (($$header_ref =~ m/$NewNokiaPattern/s) || ($$filecontent_ref =~ m/$NewNokiaPattern/s)) ) + { + printResult(HEADER_CONTEXT() . "$sep"."Proper Nokia copyright statement missing$sep$sep$lastDistributionValue$sep$fullfilename$sep$linenumtext\n"); + $verifyFailedCount[VERI_PROPER_COPYRIGHT]++; + return 0; # Failed + } + + if ($optOutputOK) + { + printResult(HEADER_CONTEXT() . "$sep"."OK$sep" . "OK$sep$lastDistributionValue$sep$fullfilename$sep$linenumtext\n"); + } + $verifyFailedCount[VERI_OK]++; + + return 1; + +} + + + +################################################## +# Verify changes for LGPL headers +################################################## +sub handleVerifyLgpl +{ + my $filecontent_ref = shift; + my $header_ref = shift; + my $commentChar = shift; + my $fullfilename = shift; + my $directory = shift; + + my $testValueLgpl = ""; + my $FromLgplText = &partialHeaderOf(LGPL_LICENSE,$commentChar, \$testValueLgpl); + + if ($lastDistributionValue eq "") + { + # Distribution file may be empty if giving single file as input + # Read it + $lastDistributionValue = readDistributionValue($directory); + } + + printLog(LOG_DEBUG, "handleVerifyLgpl $fullfilename, $$header_ref\n"); + + # Note that partial headers are manually quoted in the declaration + # Otherwise \Q$SFLText\E and \Q$EPLText\E would be needed around those ones + # because plain text contains special chars, like . + printLog(LOG_DEBUG, "handleVerifyLgpl testheaders: $testValueLgpl,$$header_ref\n"); + + if ( !( ($$header_ref =~ m/$testValueLgpl/s) || ($$filecontent_ref =~ m/$testValueLgpl/s) ) ) + { + # Header not found from header or whole file + if (isGeneratedHeader($header_ref) || isGeneratedHeader($filecontent_ref)) + { + # OK, it is generated header + if ($optOutputOK) + { + printResult(HEADER_CONTEXT() . "$sep"."OK$sep" . "Generated header$sep$lastDistributionValue$sep$fullfilename$sep$linenumtext\n"); + } + $verifyFailedCount[VERI_OK]++; + return 1; # OK + } + + my $comment = getCommentText($lastDistributionValue, 0, "0,3,7", $fullfilename); + if (($$header_ref =~ m/$OldNokiaPattern2/is) || ($$filecontent_ref =~ m/$OldNokiaPattern2/is)) + { + printResult(HEADER_CONTEXT() . "$sep"."LGPL header missing (old Nokia copyright)$sep$comment$sep$lastDistributionValue$sep$fullfilename$sep$linenumtext\n"); + } + else + { + printResult(HEADER_CONTEXT() . "$sep"."LGPL header missing$sep$comment$sep$lastDistributionValue$sep$fullfilename$sep$linenumtext\n"); + } + $verifyFailedCount[VERI_MISSING_HEADER]++; + return 0; + } + + if (!checkNoMultipleLicenses($filecontent_ref, $header_ref, $commentChar, $fullfilename, $directory)) + { + printResult(HEADER_CONTEXT() . "$sep"."Multiple licenses$sep$sep$sep$fullfilename$sep" ."1\n"); + printLog(LOG_ERROR, "Multiple licenses:". $fullfilename . "\n"); + $verifyFailedCount[VERI_MULTIPLE_LICENSES]++; + return 0; # Failed + } + + + if ($optOutputOK) + { + printResult(HEADER_CONTEXT() . "$sep"."OK$sep" . "OK$sep$lastDistributionValue$sep$fullfilename$sep$linenumtext\n"); + } + + $verifyFailedCount[VERI_OK]++; + + return 1; + +} + + +################################################## +# Test if header is already OK +# NOTE ! If header need to be converted to a new format, the +# check must return FALSE !!! +################################################## +sub checkHeader +{ + my $filecontent_ref = shift; + my $header_ref = shift; + my $commentChar = shift; + my $fullfilename = shift; + my $directory = shift; + my $req_license_ref = shift; # in/out !!! + + my $testValueSfl = ""; + my $testValueEpl = ""; + my $testValueLgpl = ""; + + my $FromSFLText = &partialHeaderOf(SFL_LICENSE,$commentChar, \$testValueSfl); + my $FromEPLText = &partialHeaderOf(EPL_LICENSE,$commentChar, \$testValueEpl); + my $FromLGPLText = &partialHeaderOf(LGPL_LICENSE,$commentChar, \$testValueLgpl); + + # Note that partial headers are manually quoted in the declaration + # Otherwise \Q$SFLText\E and \Q$EPLText\E would be needed around those ones + # because plain text contains special chars, like . + + my $retLicense = SFL_LICENSE; # default + my $testValue = $testValueSfl; + + if ($$req_license_ref == EPL_LICENSE) + { + $testValue = $testValueEpl; + $retLicense = EPL_LICENSE; + } + elsif ($$req_license_ref == LGPL_LICENSE) + { + $testValue = $testValueLgpl; + $retLicense = LGPL_LICENSE; + } + + my $ret = 0; + $ret = ($$header_ref =~ m/$testValue/s); + if (!$ret) + { + # Check the rest of file + $ret = ($$filecontent_ref =~ m/$testValue/s); + } + + printLog(LOG_DEBUG, "checkHeader return=$ret\n"); + + if ($ret) + { + $$req_license_ref = $retLicense; + } + + return $ret; +} + + +################################################## +# Test if file does not contain multiple licenses +# Returns 0 if test failed +################################################## +sub checkNoMultipleLicenses +{ + my $filecontent_ref = shift; + my $header_ref = shift; + my $commentChar = shift; + my $fullfilename = shift; + my $directory = shift; + + my $usedLicense = SFL_LICENSE; + my $licenseCnt = 0; + if (checkHeader($filecontent_ref, $header_ref, $commentChar, $fullfilename, $directory, \$usedLicense)) + { + printLog(LOG_DEBUG, "checkNoMultipleLicenses SFL: $fullfilename\n"); + $licenseCnt++; + } + + $usedLicense = EPL_LICENSE; + if (checkHeader($filecontent_ref, $header_ref, $commentChar, $fullfilename, $directory, \$usedLicense)) + { + printLog(LOG_DEBUG, "checkNoMultipleLicenses EPL: $fullfilename\n"); + $licenseCnt++; + } + + $usedLicense = LGPL_LICENSE; + if (checkHeader($filecontent_ref, $header_ref, $commentChar, $fullfilename, $directory, \$usedLicense)) + { + printLog(LOG_DEBUG, "checkNoMultipleLicenses LGPL: $fullfilename\n"); + $licenseCnt++; + } + + if ($licenseCnt > 1) + { + return 0; # check failed + } + return 1; +} + +################################################## +# Change distribution value +# Can also be called with empty file content +################################################## +sub handleDistributionValue +{ + my $filecontent_ref = shift; + my $filename = shift; + my $content = $$filecontent_ref; + + if ($optVerify) + { + # Ignored + return LICENSE_NONE; + } + + $content =~ s/\n//g; # Remove all new-lines + $content =~ s/^\s+//g; # trim left + $content =~ s/\s+$//g; # trim right + + if ($content ne "" && $content ne ZERO_DISTRIBUTION_VALUE) + { + if ($optEpl && ($content eq SFL_DISTRIBUTION_VALUE )) + { + # Allow switching SFL to EPL + $$filecontent_ref = EPL_DISTRIBUTION_VALUE; + printLog(LOG_INFO, "Distribution value changed from $content to $$filecontent_ref: $filename\n"); + return LICENSE_CHANGED; + } + else + { + # Otheriwise do not touch non-zero files ! (agreed with build team) + $ignoreCount++; + return LICENSE_NONE; + } + } + + if ($optOem) + { + # Leave existing (or missing) value as it was + return LICENSE_NONE; + } + elsif ($optEpl) + { + $$filecontent_ref = EPL_DISTRIBUTION_VALUE; + printLog(LOG_INFO, "Distribution value changed from $content to $$filecontent_ref: $filename\n"); + return LICENSE_CHANGED; + } + else # SFL + { + $$filecontent_ref = SFL_DISTRIBUTION_VALUE; + printLog(LOG_INFO, "Distribution value changed from $content to $$filecontent_ref: $filename\n"); + return LICENSE_CHANGED; + } + + return LICENSE_NONE; + +} + +################################################## +# Select proper +################################################## +sub licenceIdForOption +{ + if ($optEpl) + { + return EPL_LICENSE; + } + elsif ($optLgpl) + { + return LGPL_LICENSE; + } + else # Must be + { + return SFL_LICENSE; + } +} + + +################################################## +# Select proper header +################################################## +sub headerOf +{ + my $style = shift; + + if ($style < 0 || $style > 1) + { + printLog(LOG_ALWAYS, "INTERNAL ERROR: Header index out of bounds:$style. Exiting.\n"); + exit 1; + } + + my $ref; + if ($optEpl) + { + $ref = $EplHeaders[$style]; + } + elsif ($optLgpl) + { + $ref = $LgplHeaders[$style]; + } + else # SFL + { + $ref = $SflHeaders[$style]; + } + + # Return the actual value + return $$ref; +} + +################################################## +# Select proper partial header +################################################## +sub partialHeaderOf +{ + my $license = shift; + my $commentChar = shift; + my $testValue_ref = shift; + + my $ref; + my $ref2; + if ($license eq EPL_LICENSE) + { + $ref = $EplHeaders[2]; + $ref2 = $EplHeaders[3]; + } + elsif ($license eq S60_LICENSE) + { + $ref = $S60Headers[2]; + $ref2 = $S60Headers[3]; + } + elsif ($license eq LGPL_LICENSE) + { + $ref = $LgplHeaders[2]; + $ref2 = $LgplHeaders[3]; + } + elsif ($license eq SFL_LICENSE) + { + # SFL License + $ref = $SflHeaders[2]; + $ref2 = $SflHeaders[3]; # return value + } + else + { + printLog(LOG_ALWAYS, "INTERNAL ERROR: Invalid license parameter :$license. Exiting.\n"); + exit 1; + } + + # Switch to proper comment char + my $ret = $$ref; + $ret =~ s/$CC/$commentChar/g; # Replace the proper comment starter character + + # Return values + $$testValue_ref = $$ref2; + return $ret; +} + + +################################################## +# Print result line +################################################## +sub normalizeCppComment +{ + my $header_regexp2 = shift; + my $filecontent = shift; + my $oldheader_ref = shift; # in/out + + + # Normalize the C++ header syntax back to C++ in the file content + # in order to standardize stuff later on + $$oldheader_ref =~ s/(\/){3,}/\/\//g; # replace ///+ back to // + $$oldheader_ref =~ s/\/\//*/g; # Replace now // with * + $$oldheader_ref = "/*\n" . $$oldheader_ref . "*/\n"; # Add /* and */ markers + + # Created saved modified file content into memory + # This is the best way to do this. + my $ret = $filecontent; + $ret =~ s/$header_regexp2/$$oldheader_ref/; # Note /s not used by purpose ! + return $ret; +} + + + +################################################## +# Print result line +################################################## +sub printResult +{ + my $text = shift; + + if ($outputfile) + { + print OUTPUT $text; + } + else + { + print $text; + } + + printLog(LOG_DEBUG(), $text); + +} + +################################################## +# Print log line +################################################## +sub printLog +{ + my $loglevel = shift; + my $text = shift; + + if ($loglevel > $optLogLevel) + { + return; # No logging + } + if ($logFile) + { + print LOG $LOGTEXTS[$loglevel] . $text; + } + + return 0; +} + + +################################################## +# Print log line +################################################## +sub printLogStatisticNumber +{ + my $number = shift; + my $loglevel = shift; + my $text = shift; # Should contains %d where to put the number + + if ($number == 0) + { + return; # No logging + } + + if ($text =~ m/\%d/) + { + $text =~ s/\%d/$number/; + } + else + { + # Add number to the beginning of text + $text = $number . " " . $text; + } + + if ($loglevel > $optLogLevel) + { + return; # No logging + } + if ($logFile) + { + print LOG $LOGTEXTS[$loglevel] . $text; + } + + return 0; +} + + +################################################## +# Read the content of old output +################################################## +sub readOldOutput +{ + my($filename) = shift; + my $fh = new FileHandle "<$filename"; + if (!defined($fh)) + { + printLog(LOG_ERROR, "Could not open file $filename for read\n"); + return; + } + + my @lines = <$fh>; + my $line; + foreach $line (@lines) + { + my (@parts) = split(/\,/,$line); # Split line with "," separator + if ($parts[2] =~ m/$IGNORE_MAN/i) + { + my $fullfilename = lc($parts[4]); + $fullfilename =~ s/\\/\//g; # Standardize name + $manualIgnoreFileHash{$fullfilename} = "1" ; # Just some value + printLog(LOG_DEBUG, "Manually ignoring file:$fullfilename\n"); + } + } + + close ($fh); +} + +################################################## +# Read configuation file which has the format: +# sf-update-licence-header-config-1.0 +################################################## +sub readConfig +{ + my ($fname) = @_; + + open(IN,$fname) || die "Unable to open file: \"$fname\" for reading."; + LINE: + while() + { + chomp; + # tr/A-Z/a-z/; # Do not lowercase pattern + my $line = $_; + $line =~ s/^\s+//; # trim left + $line =~ s/\s+$//; # trim right + + next LINE if length($line) == 0; # # Skip empty lines + next LINE if ($line =~ /^\#.*/); # Skip comments; + + if ($line =~ /^sf-update-licence-header-config.*/i) + { + my ($tmp1, $tmp2) = split(/sf-update-licence-header-config-/,$line); # Get version + $configVersion = $tmp2; + } + elsif ($line =~ /^sf-distribution-id/i) + { + my ($tmp, @parts) = split(/[\s\t]+/,$line); # space as separator + my $cnt = @parts; + push(@sfDistributionIdArray, @parts); + my $cnt = @sfDistributionIdArray; + printLog(LOG_DEBUG, "readConfig:sfDistributionIdArray count:$cnt\n"); + } + elsif ($line =~ /^sf-generated-header/i) + { + my ($tmp, @parts) = split(/[\s\t]+/,$line); # space as separator + my $cnt = @parts; + push(@sfGeneratedPatternArray, @parts); + my $cnt = @sfGeneratedPatternArray; + printLog(LOG_DEBUG, "readConfig:sfGeneratedPatternArray count:$cnt\n"); + } + } + + # Pre-compile here the source line pattern + close (IN); +} + + +################################################## +# Test ID is under SF distribution +################################################## +sub isSFDistribution +{ + my $id = shift; + + if (($id == SFL_DISTRIBUTION_VALUE) || ($id == EPL_DISTRIBUTION_VALUE)) + { + # Implicit case + return 1; + } + + my $otherOkId = grep { $_ eq $id } @sfDistributionIdArray; # Use exact match + return $otherOkId; +} + +################################################## +# Test header contains generated file pattern +################################################## +sub isGeneratedHeader +{ + my $header_ref = shift; + + my $count = grep { $$header_ref =~ m/$_/is } @sfGeneratedPatternArray; + return $count; +} + + +################################################## +# MAIN +################################################## + +GetOptions( + 'h|help' => \$help, #print help message + 'm|modify' => \$optModify, #Allow modifications + 'c|create' => \$optCreate, #Create missing file + 'output:s' => \$outputfile, #Output (result) file + 'ignorefile:s' => \$ignorefilepattern, #Ignore file pattern + 'oldoutput:s' => \$oldOutputFile, #Old output file + 'log:s' => \$logFile, # Log file + 'verbose:i' => \$optLogLevel, # Logging level + 'epl' => \$optEpl, # Switch file header to EPL one + 'lgpl' => \$optLgpl, # Switch file header LGPL v2.1 + 'oem' => \$optOem, # Switch back S60 header for OEM release. + 'eula' => \$optOem, # Switch back S60 header for EULA (End-User License Agreement) release. Same as OEM + 'append' => \$optAppend, # Append result files + 'verify' => \$optVerify, # Verifies files has correct header + 'configfile:s' => \$configFile, + 'description!' => \$optDescription, # output missing description + 'okoutput!' => \$optOutputOK # output also OK entries + ); + +die $usage if $#ARGV<0; +die $usage if $help; + +if ($logFile) +{ + my $openmode = ">" . ($optAppend ? ">" : ""); + open (LOG, "$openmode$logFile") || die "Couldn't open $openmode$logFile\n"; # Can not call printLog + LOG->autoflush(1); # Force flush +} + +printLog(LOG_INFO, "========================\n"); + +if ($oldOutputFile && $optVerify) +{ + readOldOutput($oldOutputFile); +} + +if (!$configFile) +{ + $configFile = "$Bin/SFUpdateLicenceHeader.cfg"; +} + +if ($configFile && -e $configFile) +{ + &readConfig($configFile); +} + +if (!$ignorefilepattern) +{ + # Set decent default value + if ($optOem) + { + # Scan through internal stuff all source dirs just in case + $ignorefilepattern = "(_ccmwaid\.inf|\.svn)"; + } + else + { + $ignorefilepattern = "(abld\.bat|_ccmwaid\.inf|\.svn|/docs/|/internal/|/doc/)"; + } +} + +if ($optEpl) +{ + printLog(LOG_INFO, "Option -epl used\n"); +} +if ($optLgpl) +{ + printLog(LOG_INFO, "Option -lgpl used\n"); +} +if ($optOem) +{ + printLog(LOG_INFO, "Option -oem used\n"); + # Modify ignore to contain also internal dirs just in case +} +if ($optModify) +{ + printLog(LOG_INFO, "Option -modify used\n"); +} +if ($optVerify) +{ + printLog(LOG_INFO, "Option -verify used\n"); +} +if ($optCreate) +{ + printLog(LOG_INFO, "Option -create used\n"); +} + +if ($ignorefilepattern) +{ + printLog(LOG_INFO, "Option -ignorefile has value: $ignorefilepattern\n"); +} + +my $startTime = time; + +if ($outputfile) +{ + my $openmode = ">" . ($optAppend ? ">" : ""); + open (OUTPUT, "$openmode$outputfile") || die "Couldn't open $outputfile\n"; + OUTPUT->autoflush(1); # Force flush +} + +if (! -e $ARGV[0] ) +{ + printLog(LOG_ERROR, "$ARGV[0] not found\n"); + if ($logFile) + { + close LOG; + } + exit(1); +} + +printLog(LOG_INFO,"SFUpdateLicenceHeader.pl version " . VERSION . " statistics:\n"); +printLog(LOG_INFO, "Directory/file=@ARGV\n"); + +# +# Process files in the given directory recursively +# +# NOTE : "no_chdir" option not used --> find changes the current working directory +find({ wanted => \&process_file, postprocess => \&postprocess, preprocess => \&preprocess }, @ARGV); + +if ($outputfile) +{ + close OUTPUT; +} + +my $elapsedTime = time - $startTime; + +printLogStatisticNumber($fileCount, LOG_INFO, "%d files checked\n") ; +if ($optModify) +{ + printLogStatisticNumber($modifiedFileCount, LOG_INFO, "%d files modified \n") ; +} +else +{ + printLogStatisticNumber($willModifiedFileCount, LOG_INFO, "%d will be modified \n") ; +} +printLogStatisticNumber($ignoreCount, LOG_INFO, "%d files ignored.\n") ; +printLogStatisticNumber($unrecogCount, LOG_INFO, "%d files not recognized.\n") ; +if ($optVerify) +{ + for (my $i=0; $i < @verifyFailedCountMsgs; $i++) + { + printLogStatisticNumber($verifyFailedCount[$i], LOG_INFO, "Verify statistics:$verifyFailedCountMsgs[$i]=%d.\n") ; + } +} +elsif (!$optOem) +{ + printLogStatisticNumber($noDescrcount, LOG_INFO, "%d files has no Description.\n") ; + printLogStatisticNumber($NokiaCopyrCount, LOG_INFO, "%d files has Nokia copyright.\n") ; + printLogStatisticNumber($ExternalToNokiaCopyrCount, LOG_INFO, "%d files moved also to Nokia.\n") ; + printLogStatisticNumber($otherCopyrCount, LOG_INFO, "%d files has non-nokia copyright.\n") ; + printLogStatisticNumber($NoCopyrCount, LOG_INFO, "%d files has no copyright.\n") ; + printLogStatisticNumber($UnclearCopyrCount, LOG_INFO, "%d files has UNCLEAR copyright.\n") ; + printLogStatisticNumber($createCount, LOG_INFO, "%d new files.\n") ; + if ($optEpl) + { + printLogStatisticNumber($SflToEplChanges, LOG_INFO, "%d files changes from SFL to EPL license.\n") ; + } + else + { + printLogStatisticNumber($EplToSflChanges, LOG_INFO, "%d files changes from SFL to EPL license.\n") ; + } +} +else +{ + printLogStatisticNumber($SflToS60Changes, LOG_INFO, "%d files changes from SFL to S60 license.\n") ; + # printLog($EplToS60Changes, LOG_INFO, "%d files changes from EPL to S60 license.\n") ; + printLogStatisticNumber($LicenseChangeErrors, LOG_INFO, "%d errors upon license change.\n") ; +} + +printLog(LOG_INFO,"Time elapsed $elapsedTime.\n") ; + +if ($logFile) +{ + close LOG; +} +