97

I'm looking for a tool that will, in bulk, add a license header to some source files, some of which already have the header. Is there a tool out there that will insert a header, if it is not already present?

Edit: I am intentionally not marking an answer to this question, since answers are basically all environment-specific and subjective

Honinbo Shusaku
  • 1,411
  • 2
  • 27
  • 45
Alex Lyman
  • 15,637
  • 3
  • 38
  • 42
  • 5
    "I am intentionally not marking an answer to this question, since answers are basically all environment-specific and subjective" Are you looking for an environment agnostic solution, such as pseudo code? If not, please let us know what environment you're working with. – jrummell Mar 20 '09 at 20:41
  • 1
    jrummell: No, not looking for a environment-agnostic solution. Was looking for things that a multiple-environment team I was on could use. – Alex Lyman Mar 21 '09 at 05:26
  • would a windows UI app that let you do this, be an acceptable answer? – Brady Moritz Sep 02 '11 at 18:12
  • @boomhauer I am looking for a windows UI app. Do you know of any? – Jus12 Oct 26 '11 at 19:30
  • I added a new answer below, it should do just this. – Brady Moritz Oct 30 '11 at 03:32
  • Justin, the answer I posted below should have met your requirements, pleace mark it as answer if so, or comment on how it is not the right solution. Thanks – Brady Moritz Nov 28 '11 at 14:43
  • @AlexLyman. Not choosing an answer is a dick move. – Mad Physicist Sep 23 '14 at 17:23
  • @MadPhysicist Is that why you started this close vote for a perfectly good question? – CrazyCasta Oct 02 '14 at 16:01
  • @CrazyCasta. The reason for the close vote is pretty self explanatory in the auto-generated blurb. Roughly speaking, it boild down to Google thoroughly, then ask. – Mad Physicist Oct 02 '14 at 21:14

12 Answers12

72
#!/bin/bash

for i in *.cc # or whatever other pattern...
do
  if ! grep -q Copyright $i
  then
    cat copyright.txt $i >$i.new && mv $i.new $i
  fi
done
joshperry
  • 41,167
  • 16
  • 88
  • 103
Tim
  • 969
  • 8
  • 4
  • 1
    for i in "$@" is a pretty good choice. You can also get inventive with checkouts if your VCS system needs those. – Jonathan Leffler Sep 30 '08 at 03:57
  • 11
    -1, you should quote ``"$i"`` – Aleks-Daniel Jakimenko-A. Apr 15 '14 at 20:43
  • I guess this doesn't work in subdirectories recursively :-( – knocte Feb 21 '18 at 11:11
  • 5
    @knocte Replace the for-loop with this `for i in $(find /folder -name '*.cc');` to run the script on subdirectories – Joyce Mar 29 '18 at 10:20
  • Here is the one-liner solution https://github.com/apache/skywalking-eyes#fix-license-header You can even use it in GitHub Actions to check pull requests, review by adding missing license headers suggestions, and fix the headers automatically – kezhenxu94 Jun 21 '21 at 10:33
  • Remember to have a newline at the end of the file in copyright.txt as otherwise the script will replace the first line of every file it runs on. (or so it did on my machine anyway) – Newbyte Oct 19 '21 at 15:03
18

Here's a Bash script that'll do the trick, assuming you have the license header in the file license.txt:

File addlicense.sh:

#!/bin/bash  
for x in $*; do  
head -$LICENSELEN $x | diff license.txt - || ( ( cat license.txt; echo; cat $x) > /tmp/file;  
mv /tmp/file $x )  
done  

Now run this in your source directory:

export LICENSELEN=`wc -l license.txt | cut -f1 -d ' '`  
find . -type f \(-name \*.cpp -o -name \*.h \) -print0 | xargs -0 ./addlicense.sh  
Community
  • 1
  • 1
Adam Rosenfield
  • 390,455
  • 97
  • 512
  • 589
16

Check out the copyright-header RubyGem. It supports files with extensions ending in php, c, h, cpp, hpp, hh, rb, css, js, html. It can also add and remove headers.

Install it by typing "sudo gem install copyright-header"

After that, can do something like:

copyright-header --license GPL3 \
  --add-path lib/ \
  --copyright-holder 'Dude1 <dude1@host.com>' \
  --copyright-holder 'Dude2 <dude2@host.com>' \
  --copyright-software 'Super Duper' \
  --copyright-software-description "A program that makes life easier" \
  --copyright-year 2012 \
  --copyright-year 2012 \
  --word-wrap 80 --output-dir ./

It also supports custom license files using the --license-file argument.

Erik Osterman
  • 559
  • 4
  • 7
  • This is great except that it doesn't remove custom existing headers :( – pgpb.padilla Feb 12 '14 at 02:41
  • 3
    You can remove existing headers if you create a template for them. Pass the template as an argument to the script with the `--license-file` argument, and use the `--remove-path` flag to strip that exact header from all the files. Basically, there are so many different types of headers, creating an algorithm to reliably remove them is non-trivial. – Erik Osterman Feb 19 '14 at 10:23
  • 1
    We recently added a `Dockerfile` so installing onerous ruby dependencies is no longer a problem – Erik Osterman Mar 14 '17 at 00:57
16

Python 2 solution, modify for your own need

Features:

  • handles UTF headers (important for most IDEs)
  • recursively updates all files in target directory passing given mask (modify the .endswith parameter for the filemask of your language (.c, .java, ..etc)
  • ability to overwrite previous copyright text (provide old copyright parameter to do this)
  • optionally omits directories given in the excludedir array
# updates the copyright information for all .cs files
# usage: call recursive_traversal, with the following parameters
# parent directory, old copyright text content, new copyright text content

import os

excludedir = ["..\\Lib"]

def update_source(filename, oldcopyright, copyright):
    utfstr = chr(0xef)+chr(0xbb)+chr(0xbf)
    fdata = file(filename,"r+").read()
    isUTF = False
    if (fdata.startswith(utfstr)):
        isUTF = True
        fdata = fdata[3:]
    if (oldcopyright != None):
        if (fdata.startswith(oldcopyright)):
            fdata = fdata[len(oldcopyright):]
    if not (fdata.startswith(copyright)):
        print "updating "+filename
        fdata = copyright + fdata
        if (isUTF):
            file(filename,"w").write(utfstr+fdata)
        else:
            file(filename,"w").write(fdata)

def recursive_traversal(dir,  oldcopyright, copyright):
    global excludedir
    fns = os.listdir(dir)
    print "listing "+dir
    for fn in fns:
        fullfn = os.path.join(dir,fn)
        if (fullfn in excludedir):
            continue
        if (os.path.isdir(fullfn)):
            recursive_traversal(fullfn, oldcopyright, copyright)
        else:
            if (fullfn.endswith(".cs")):
                update_source(fullfn, oldcopyright, copyright)
    
     
oldcright = file("oldcr.txt","r+").read()
cright = file("copyrightText.txt","r+").read()
recursive_traversal("..", oldcright, cright)
exit()
Silver Dragon
  • 5,480
  • 6
  • 41
  • 73
13

Edit: If you're using eclipse, there's a plugin

I wrote a simple python script based on Silver Dragon's reply. I needed a more flexible solution so I came up with this. It allows you to add a headerfile to all files in a directory, recursively. You can optionally add a regex which the filenames should match, and a regex wich the directory names should match and a regex which the first line in the file shouldn't match. You can use this last argument to check if the header is already included.

This script will automatically skip the first line in a file if this starts with a shebang (#!). This to not break other scripts that rely on this. If you do not wish this behaviour you'll have to comment out 3 lines in writeheader.

here it is:

#!/usr/bin/python
"""
This script attempts to add a header to each file in the given directory 
The header will be put the line after a Shebang (#!) if present.
If a line starting with a regular expression 'skip' is present as first line or after the shebang it will ignore that file.
If filename is given only files matchign the filename regex will be considered for adding the license to,
by default this is '*'

usage: python addheader.py headerfile directory [filenameregex [dirregex [skip regex]]]

easy example: add header to all files in this directory:
python addheader.py licenseheader.txt . 

harder example adding someone as copyrightholder to all python files in a source directory,exept directories named 'includes' where he isn't added yet:
python addheader.py licenseheader.txt src/ ".*\.py" "^((?!includes).)*$" "#Copyright .* Jens Timmerman*" 
where licenseheader.txt contains '#Copyright 2012 Jens Timmerman'
"""
import os
import re
import sys

def writeheader(filename,header,skip=None):
    """
    write a header to filename, 
    skip files where first line after optional shebang matches the skip regex
    filename should be the name of the file to write to
    header should be a list of strings
    skip should be a regex
    """
    f = open(filename,"r")
    inpt =f.readlines()
    f.close()
    output = []

    #comment out the next 3 lines if you don't wish to preserve shebangs
    if len(inpt) > 0 and inpt[0].startswith("#!"): 
        output.append(inpt[0])
        inpt = inpt[1:]
        
    if skip and skip.match(inpt[0]): #skip matches, so skip this file
        return
    
    output.extend(header) #add the header
    for line in inpt:
        output.append(line)
    try:
        f = open(filename,'w')
        f.writelines(output)
        f.close()
        print "added header to %s" %filename
    except IOError,err:
        print "something went wrong trying to add header to %s: %s" % (filename,err)


def addheader(directory,header,skipreg,filenamereg,dirregex):
    """
    recursively adds a header to all files in a dir
    arguments: see module docstring
    """
    listing = os.listdir(directory)
    print "listing: %s " %listing
    #for each file/dir in this dir
    for i in listing:
        #get the full name, this way subsubdirs with the same name don't get ignored
        fullfn = os.path.join(directory,i) 
        if os.path.isdir(fullfn): #if dir, recursively go in
            if (dirregex.match(fullfn)):
                print "going into %s" % fullfn
                addheader(fullfn, header,skipreg,filenamereg,dirregex)
        else:
            if (filenamereg.match(fullfn)): #if file matches file regex, write the header
                writeheader(fullfn, header,skipreg)


def main(arguments=sys.argv):
    """
    main function: parses arguments and calls addheader
    """
    ##argument parsing
    if len(arguments) > 6 or len(arguments) < 3:
        sys.stderr.write("Usage: %s headerfile directory [filenameregex [dirregex [skip regex]]]\n" \
                         "Hint: '.*' is a catch all regex\nHint:'^((?!regexp).)*$' negates a regex\n"%sys.argv[0])
        sys.exit(1)
    
    skipreg = None
    fileregex = ".*"
    dirregex = ".*"
    if len(arguments) > 5:
        skipreg = re.compile(arguments[5])
    if len(arguments) > 3:
        fileregex =  arguments[3]
    if len(arguments) > 4:
        dirregex =  arguments[4]
    #compile regex    
    fileregex = re.compile(fileregex)
    dirregex = re.compile(dirregex)
    #read in the headerfile just once
    headerfile = open(arguments[1])
    header = headerfile.readlines()
    headerfile.close()
    addheader(arguments[2],header,skipreg,fileregex,dirregex)

#call the main method
main()
Jens Timmerman
  • 9,316
  • 1
  • 42
  • 48
12

For Java you can use Maven's License plugin: http://code.google.com/p/maven-license-plugin/

Flow
  • 23,572
  • 15
  • 99
  • 156
marcospereira
  • 12,045
  • 3
  • 46
  • 52
11

Ok here is a simple windows-only UI tool that searches for all files of your specified type in a folder, prepends the text you desire to the top (your license text), and copies the result to another directory (avoiding potential overwrite problems). It's also free. Required .Net 4.0.

I am actually the author, so feel free to request fixes or new features... no promises on delivery schedule though. ;)

more info: License Header tool at Amazify.com

Brady Moritz
  • 8,624
  • 8
  • 66
  • 100
5

Check out license-adder. It supports multiple code files (even custom ones) and handles existing headers correctly. Comes already with templates for the most common Open Source licenses.

  • 1
    Thanks for this, which `license-adder` are you exactly referring to? I've found [license-adder - free .NET application - Google Project Hosting](https://code.google.com/p/license-adder/), and [License-Adder · simple python script · GitHub](https://github.com/gzog/License-Adder) – sdaau Jul 04 '14 at 15:18
  • GitHub now finds: https://github.com/sanandrea/License-Adder – koppor Jan 20 '17 at 06:47
4

Here is one I rolled in PHP to modify PHP files. I also had old license information to delete so it replaces the old text first, then adds the new text immediately after the opening

<?php
class Licenses
{
    protected $paths = array();
    protected $oldTxt = '/**
 * Old license to delete
 */';
    protected $newTxt = '/**
 * @license    http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
 */';

    function licensesForDir($path)
    {
        foreach(glob($path.'/*') as $eachPath)
        {
            if(is_dir($eachPath))
            {
                $this->licensesForDir($eachPath);
            }
            if(preg_match('#\.php#',$eachPath))
            {
                $this->paths[] = $eachPath;
            }
        }
    }

    function exec()
    {

        $this->licensesForDir('.');
        foreach($this->paths as $path)
        {
            $this->handleFile($path);
        }
    }

    function handleFile($path)
    {
        $source = file_get_contents($path);
        $source = str_replace($this->oldTxt, '', $source);
        $source = preg_replace('#\<\?php#',"<?php\n".$this->newTxt,$source,1);
        file_put_contents($path,$source);
        echo $path."\n";
    }
}

$licenses = new Licenses;
$licenses->exec();
Josh Ribakoff
  • 2,948
  • 3
  • 27
  • 26
3

Here's one I found on the Apache list. Its written in Ruby and seems easy enough to read. You should even be able to call it from rake for extra special niceness. :)

Richard Hurt
  • 2,049
  • 24
  • 32
1

if you are using sbt, there is https://github.com/Banno/sbt-license-plugin

Martijn
  • 11,964
  • 12
  • 50
  • 96
1

If you still need one, there is a little tool I have written, named SrcHead. You can find it at http://www.solvasoft.nl/downloads.html

  • 3
    From the download page: "It is written for Windows and needs the .NET Framework 2.0 to work." – Riccardo Murri Aug 31 '12 at 16:35
  • Adds C/C++ Style headers and a Unicode BOM. Meaning: The content of `header.txt` is prepended with `//` at each line and the first line starts with the Unicode BOM. – koppor Jan 20 '17 at 07:20