123

My iOS application uses a number of third party components licensed under Apache 2.0 and similar licenses, which requires me to include various bits of text, this kind of thing:

* Redistributions in binary form must reproduce the above copyright
  notice, this list of conditions and the following disclaimer in the
  documentation and/or other materials provided with the distribution.

There seems to be a reasonable precedent for putting this information under a 'License' subentry in settings bundle (on the ipad facebook, pages, keynote, numbers and wikipanion all seem to do this).

I'm struggling a bit to actually achieve the same though; I seem to need to split the text up line by line and enter into xcode a line at a time (and xcode4 seems to have a crashing problem when editing the plists).

It seems like the kind of thing that there's almost certainly a somewhere script to do, or some simple way to do it that I've missed.

JasonMArcher
  • 14,195
  • 22
  • 56
  • 52
JosephH
  • 37,173
  • 19
  • 130
  • 154

11 Answers11

198

I think I've now managed to solve all the problems I was running into.

  • It seems to be best to use group element titles to hold the licenses (this is what Apple do in the iWork apps). There is however a limit on the length of these (and I've not yet discovered exactly what the limit is), so you need to break each license file into multiple strings.
  • You can create a line break within these by include a literal carriage return (ie. otherwise known as ^M, \r or 0x0A)
  • Make sure not to include any literal "s mid-text. If you do, some or all of the strings in the file will get silently ignored.

I've got a convenience script I use to help generate the .plist and .strings file, shown below.

To use it:

  1. Create a 'licenses' directory under your project
  2. Put script into that directory
  3. Put each license into that directory, one per file, with filenames that end .license
  4. Perform any necessary reformatting on the licenses. (eg. remove extra spaces at the beginning of lines, ensure that there are no line breaks mid-paragraph). There should be a blank line in-between each paragraph
  5. Change to licenses directory & run the script
  6. Edit your settings bundle Root.plist to include a child section called 'Acknowledgements'

Here's the script:

#!/usr/bin/perl -w

use strict;

my $out = "../Settings.bundle/en.lproj/Acknowledgements.strings";
my $plistout =  "../Settings.bundle/Acknowledgements.plist";

unlink $out;

open(my $outfh, '>', $out) or die $!;
open(my $plistfh, '>', $plistout) or die $!;

print $plistfh <<'EOD';
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>StringsTable</key>
        <string>Acknowledgements</string>
        <key>PreferenceSpecifiers</key>
        <array>
EOD
for my $i (sort glob("*.license"))
{
    my $value=`cat $i`;
    $value =~ s/\r//g;
    $value =~ s/\n/\r/g;
    $value =~ s/[ \t]+\r/\r/g;
    $value =~ s/\"/\'/g;
    my $key=$i;
    $key =~ s/\.license$//;

    my $cnt = 1;
    my $keynum = $key;
    for my $str (split /\r\r/, $value)
    {
        print $plistfh <<"EOD";
                <dict>
                        <key>Type</key>
                        <string>PSGroupSpecifier</string>
                        <key>Title</key>
                        <string>$keynum</string>
                </dict>
EOD

        print $outfh "\"$keynum\" = \"$str\";\n";
        $keynum = $key.(++$cnt);
    }
}

print $plistfh <<'EOD';
        </array>
</dict>
</plist>
EOD
close($outfh);
close($plistfh);

Setting up your Settings.bundle

If you haven't created a Settings.bundle, go to File --> New --> New File...

Under the Resource section, find the Settings Bundle. Use the default name and save it to the root of your project.

Expand the Settings.bundle group and select Root.plist. You will need to add a new section where its key will be Preference Items of type Array. Add the following information:

enter image description here

The Filename key points to the plist that was created by this script. You can change the title to what ever you want.

Execute Script At Build Time

Also, if you want this script to run whenever you build your project, you can add a build phase to your target:

  1. Go to your project file
  2. Select the target
  3. Click the Build Phases tab
  4. In the lower right corner of that pane, click on 'Add Build Phase'
  5. Select 'Add Run Script'
  6. Drag and drop your perl script into the section for your script. Modify to look something like this:
  1. cd $SRCROOT/licenses ($SRCROOT points to the root of your project)
  2. ./yourScriptName.pl

After you have finished that, you can drag the Run Script build phase sooner in the build process. You'll want to move it up before Compile Sources so that the updates to your Settings Bundle get compiled and copied over.

Update for iOS 7: iOS 7 seems to handle the "Title" key different and is messing up the rendered text. To fix that the generated Acknowledgements.plist needs to use the "FooterText" key instead of "Title". This how to change the script:

for my $str (split /\r\r/, $value)
{
    print $plistfh <<"EOD";
            <dict>
                    <key>Type</key>
                    <string>PSGroupSpecifier</string>
                    <key>FooterText</key> # <= here is the change
                    <string>$keynum</string>
            </dict>
 EOD

    print $outfh "\"$keynum\" = \"$str\";\n";
    $keynum = $key.(++$cnt);
}
Toby Speight
  • 27,591
  • 48
  • 66
  • 103
JosephH
  • 37,173
  • 19
  • 130
  • 154
  • 1
    What a fantastic idea! I started doing this manually before I quickly realized I needed an automated solution, particularly because of the draconian group title length limit. – Hilton Campbell Jul 06 '11 at 15:34
  • @JosephH No probs! I hadn't done anything with the Settings.bundle before and I spent a few moments trying different things to get this to work. I figured if someone else had this problem they would benefit from the addition. I also didn't want to have to remember to run this script anytime I modified something, so adding a build phase is just what was needed. :) – Wayne Hartman Jul 08 '11 at 12:51
  • I'm not that familiar with Perl but it looks like `$keynum` isn't interpolated in the heredoc unless `EOD` is unquoted. I don't have edit privileges otherwise I'd just fix it myself :) – RandomEtc Feb 14 '12 at 01:13
  • @RandomEtc double quotes allow interpolation of variables etc, it's just single quotes that wouldn't. The code is definitely working with the double quotes there :) – JosephH Feb 14 '12 at 08:46
  • It's working for me now too - I briefly had `$keynum` in the plist but I can't reproduce that error now - must have been something else (I had indenting issues when I copy-pasted). Thanks for the script! – RandomEtc Feb 14 '12 at 21:20
  • 9
    One thing to watch out for: "Filename" is the display form of the key. The actual key is "File". If the child pane isn't showing up, right-click and select "Show Raw Keys/Values" and be sure that the key name is "File". – atticus Apr 08 '12 at 23:46
  • 10
    This is fantastic, thanks. I used 'cd "$SRCROOT/Licenses/"' in the Run Script block which works a bit better if you have multiple people working on a project. – chris Dec 05 '12 at 15:05
  • Hey guys, this tool looks great. I can't get it to display properly. I followed all advice in these comments. It looks like the pl script is generating every thing properly. When I open the settings, I see the "Acknowledgements" disclosure, but it is empty. I have taken a few screenshots of the files here: http://imgur.com/a/MhZjH. What do you think the issue could be? – VaporwareWolf Apr 15 '13 at 20:07
  • @HCHogan I'm not sure - you're probably best asking that as a new question – JosephH Apr 16 '13 at 09:40
  • 1
    For the reference of others, I got this working. It was two things. 1.) the content of the license files had some strange characters in it and 2.) For what ever reason, I have to delete the app from the simulator, then run from xcode again to get it to refresh the changes. Annoying, but it works. Great solution. – VaporwareWolf Apr 16 '13 at 17:04
  • @HCHogan & JosephH I had same problem, but on device (both iPad3 and iPhone4). Seems that deploying this change over the top of an existing app shows a empty 'Acknowledgements' disclosure page in settings. Delete app off device, re-install, and it shows up. Anyone know why? Seems like a flaw that will prevent users updating the app from getting the proper acknowledgements disclosure. – mattorb Jul 03 '13 at 16:09
  • @mpstx xcode does a different process when 'installing' an app compared to a 'real' install done by springboard - to test the upgrade process end users will see, you'll need to do before/after adhoc builds, install before build then upgrade to after build (ie. installing from web or via itunes, not with xcode) - I'd wager that it's likely to be okay, and the problem is related to something xcode is doing. – JosephH Jul 03 '13 at 16:35
  • 9
    Read https://devforums.apple.com/message/894791#894791 if you are using this with iOS7. Specifically you may need to change Title to FooterText to get it to look normal. – esilver Sep 27 '13 at 04:13
  • 1
    In iOS7 text gets messed up for me. I tried changing to `FooterText` but that didn't help. This is how it looks for me: http://s24.postimg.org/vukxsfcth/Screen_Shot_2013_09_30_at_12_34_07.png – Peter Warbo Sep 30 '13 at 10:35
  • @PeterWarbo Did you try deleting the app from the device after making the FooterText change? Sometimes iOS doesn't seem to pick up the changes otherwise. – JosephH Sep 30 '13 at 15:21
  • @PeterWarbo You need to change `Title` to `FooterText` in the Acknowledgements.plist, which is generated! **So you need to change the script!** I will update the post with to reflect the needed change. – sofacoder Feb 02 '14 at 10:40
  • Like some others I ran into problems getting the text to update. Doing a Clean on the project seems to help. – Bryce Thomas Mar 28 '14 at 08:44
  • Wow, this is brilliant! Thanks so much. Just one thing to watch out for: the spelling of license. I'm Australian and I spell it the British way: licence. It took me a bit of head scratching before I realised the script was looking for files ending in 'license'! – Jarrod Robins May 08 '14 at 07:35
  • 1
    I have followed the tutorial exactly, deleted and reinstalled my app, cleaned the project, but my acknowledgements page is still completely blank. Does anyone know how to fix this? – Andrew Jun 11 '14 at 01:22
  • @Andrew are your .plist and .strings files being generated into Settings.bundle? – Rob Jun 15 '14 at 05:31
  • @Rob, yes they are. However, when I click on the .plist file is says "The data couldn’t be read because it isn’t in the correct format." – Andrew Jul 03 '14 at 18:11
  • When I try to run the script I get this error: Missing right curly or square bracket at (file path) line 57, at end of line. Any ideas? – Jacob Jul 28 '14 at 03:31
  • @Jacob The script given above doesn't seem to have that syntax error, I just copied it and it ran fine; I'd try C&Ping it again, or see http://stackoverflow.com/questions/1793305/how-do-i-fix-the-perl-syntax-error-missing-right-curly-or-square-bracket-using – JosephH Jul 28 '14 at 09:25
  • @JosephH hmmm, I have C&P'd it many times and it is still doing this :/ That's alright I'll just do it manually :( – Jacob Jul 28 '14 at 16:31
  • 1
    @JosephH do you think you could upload a sample project? I'm still having trouble with it and would like to use it in future projects. – Jacob Jul 28 '14 at 22:03
  • For some strange reason, it only works form me if I do the following after the tutorial: 1. Change "Strings Filename" to "Acknowledgements" from "Root" 2. Restart the app 3. Remove the app from the simulator 4. Change "Strings Filename" back to "Root" 5. Run the app again if anyone understands why, please tell – Arjan Aug 21 '14 at 09:20
  • In XCode6 there is no option to set the type to Child pane for Item 0. How do we set this to be a Child pane? – Marc Oct 07 '15 at 02:50
  • 2
    How to make it a child pane: Create Item 0 inside Preference Items. It looks like you cannot select Child Pane as the type, but what you do is create an item under Item 0 (s subitem of Item 0) and that first item has key “Type”. To make it easier, right click on this and select Show Raw Keys/Values. You want to set the value for Type to be PSChildPaneSpecifier. Once you unset Show Raw Keys/Values it will now say Child Pane, even in the name for Item 0, i.e. “Its 0 (Child Pane - )”. This is what you want. – Marc Oct 07 '15 at 03:03
  • If I'm putting in only a licence or 5 which is what 90% of the apps out there needs, I don't need to do all the fancy scripting to put it in and setting it up and test out one more piece of code. How do you do it by hand? This is only worth it if I have 20-50 licences – mskw Jan 06 '18 at 19:52
  • Tip in case it saves someone else time—I've got a space in the path to my project, so I needed to use `cd "${SRCROOT}/licenses"` rather than `cd $SRCROOT/licenses` to get the build script to work. – George WS Jul 29 '21 at 05:14
37

Here's the same solution that @JosephH provided (without translations), but done in Python for anyone who prefers python over perl

import os
import sys
import plistlib
from copy import deepcopy

os.chdir(sys.path[0])

plist = {'PreferenceSpecifiers': [], 'StringsTable': 'Acknowledgements'}
base_group = {'Type': 'PSGroupSpecifier', 'FooterText': '', 'Title': ''}

for filename in os.listdir("."):
    if filename.endswith(".license"):
        current_file = open(filename, 'r')
        group = deepcopy(base_group)
        title = filename.split(".license")[0]
        group['Title'] = title
        group['FooterText'] = current_file.read()
        plist['PreferenceSpecifiers'].append(group)

plistlib.writePlist(
    plist,
    "../Settings.bundle/Acknowledgements.plist"
)
Sean
  • 751
  • 9
  • 10
  • This answer needs more upvotes. Python syntax `<3` Perl syntax. ;) – Ricardo Sanchez-Saez Mar 25 '14 at 23:45
  • 5
    `current_file = codecs.open(filename, 'r', 'utf-8')` for unicode licenses. – user2821144 Jan 18 '15 at 20:04
  • Thanks! I've created a new version based on this one that is great for Carthage. It also removes some unnecessary newlines from the license text. Check it out: https://gist.github.com/Zyphrax/0d015c618d46093b4f815e62a6a33969 – Yvo May 29 '17 at 23:22
  • 1
    I've put a bit more work in it, see: https://github.com/Building42/AckAck – Yvo Jun 03 '17 at 21:36
16

As an alternative, for those using CocoaPods, it will generate an 'Acknowledgements' plist for each target specified in your Podfile which contains the License details for each Pod used in that target (assuming details have been specified in the Pod spec). The property list file that can be added to the iOS settings bundle.

There's also projects under way to allow this data to be converted and displayed within the app instead:

https://github.com/CocoaPods/cocoapods-install-metadata

https://github.com/cocoapods/CPDAcknowledgements

JosephH
  • 37,173
  • 19
  • 130
  • 154
  • 3
    Here's more info on it: https://github.com/CocoaPods/CocoaPods/wiki/Acknowledgements – Oren Oct 09 '14 at 03:13
  • Be aware that the Cocoapods wiki has the input file name as Pods-Acknowledgements.plist but the file is actually generated as Pods-acknowledgements.plist (lowercase 'a'). Using the wrong case will break pod install if you file system is case-sensitive. – Keller Mar 16 '16 at 17:38
15

I thought I'd throw my iteration on Sean's awesome python code in the mix. The main difference is that it takes an input directory and then recursively searches it for LICENSE files. It derives the title value from the parent directory of the LICENSE file, so it plays well with cocoapods.

The motivation was to create a build script to automatically keep the legal section of my app up to date as I add or remove pods. It also does some other things like remove forced newlines from licenses so the paragraphs look a bit better on the devices.

https://github.com/carloe/LicenseGenerator-iOS

enter image description here

carloe
  • 1,440
  • 3
  • 18
  • 26
  • Usage desc says ```./credits.py -s "$SRCROOT" -o "$SRCROOT/Project/Settings.bundle/Credits.plist"``` where you substitute ```Project``` with your actual project name. Hope it helps someone. – app4g Mar 02 '22 at 09:49
8

I made a script in Ruby inspiered by @JosephH script. This version will, in my own opinion, better represent the individual open source projects.

Wisit iOS-AcknowledgementGenerator to download the script and sample project.

This is what acknowledgements will look like in your App:

Settings.app Settings.bundle Acknowledgements enter image description here

cvknage
  • 268
  • 1
  • 3
  • 9
2

This is an addendum to JosephH's answer. (I don't have the rep to comment)

I had to move <key>StringsTable</key> <string>Acknowledgements</string> down to above the last </dict> in the Perl script.

Before this modification, the Acknowledgements Section in the App was empty and XCode couldn't read the resulting Acknowledgements.plist. ( "The data couldn’t be read because it isn’t in the correct format.")

(XCode 6.3.2 iOS 8.3)

mattti
  • 53
  • 5
2

The Python script from Sean in this thread works. But there a couple of basic things to know.

  1. in Xcode, right click on the top of the Project Navigator tree, on the name of your project, and add a New Group. This puts a new folder in your project.
  2. Add Sean's script there and make sure to save it as: Acknowledgements.py.
  3. Make sure you have Python installed on your system. I'm using a Mac.
  4. Add a first license file to the folder you created in 1. Make it simple like just having one word in the file, say: Testing. Save it in the folder as Test1.license.
  5. Set up your Settings.bundle as per JosephH above.
  6. Use your Terminal app to CD to the folder you created in 1.
  7. Run the script. Type: python Acknowledgements.py. If you get no errors it will return right back to the Terminal prompt. Do all of this before adding any run script to the Build.
  8. Build and run your app.
  9. Double tap on the iPhone home button and kill Settings. It doesn't often pick up the Settings change for your app until Settings restarts.
  10. After restarting Settings, go to your app and look to see if it worked.
  11. If that all worked, slowly add more license files but run the script each time. You can get errors running the script because of certain characters in the file so the easy way to debug is to add a file, run the script, see if it worked and proceed. Else, edit any special characters out of the .license file.
  12. I did not get the Run Build Script work per the instructions above. But this process works fine if you are not changing the .license files that often.
D. Rothschild
  • 659
  • 9
  • 14
1

Ack Ack: Acknowledgement Plist Generator
A while back I've created a Python script that scans for license files and creates a nice Acknowledgements plist that you can use in your Settings.plist. It does a lot of the work for you.

https://github.com/Building42/AckAck

Features

  • Detects Carthage and CocoaPods folders
  • Detects existing plists for custom licenses
  • Cleans up the license texts by removing unnecessary new lines and line breaks
  • Provides many customization options (see --help for details)
  • Supports both Python v2 and v3

Install

wget https://raw.githubusercontent.com/Building42/AckAck/master/ackack.py
chmod +x ackack.py

Run

./ackack.py

Screenshot

Acknowledgements

If you have suggestions for improvements, feel free to post an issue or pull request on GitHub!

Yvo
  • 18,681
  • 11
  • 71
  • 90
0

Aknowlist is a strong CocoaPod candidate that is actively maintained at the time of this writing. It automates this licensing if you are okay with housing the licenses in your app rather than the settings bundle. It worked great for the project I was working on.

0

I had to modify sean's script for modern python3:

import os
import sys
import plistlib
from copy import deepcopy

os.chdir(sys.path[0])

plist = {'PreferenceSpecifiers': [], 'StringsTable': 'Acknowledgements'}
base_group = {'Type': 'PSGroupSpecifier', 'FooterText': '', 'Title': ''}

for filename in os.listdir("."):
    if filename.endswith(".license"):
        with open(filename, 'r') as current_file:
            group = deepcopy(base_group)
            title = filename.split(".license")[0]
            group['Title'] = title
            group['FooterText'] = current_file.read()
            plist['PreferenceSpecifiers'].append(group)

with open("Acknowledgements.plist", "wb") as f:
    plistlib.dump(plist, f)
Justin Meiners
  • 10,754
  • 6
  • 50
  • 92
-1

Wait, license notation is not a setting.

Edit: I think license notice is not a setting. I think it is irrational to expect users who want to check the license notice to open the settings app. Therefore, I thought we should create a page for the license notice in the appropriate place in the app.

iwatachan
  • 159
  • 2
  • 8
  • This does not provide an answer to the question. To critique or request clarification from an author, leave a comment below their post. - [From Review](/review/low-quality-posts/33548437) – Mafii Jan 03 '23 at 10:54
  • I’m sorry. I read https://meta.stackoverflow.com/questions/268190/answers-that-use-a-different-technology-than-what-is-asked and added details and alternatives to the answer. – iwatachan Jan 03 '23 at 13:38