Browse Source

Merge pull request #18899 from ibrahn/gdscript-license-info

GDScript access to copyright, license, author and donor information.
Rémi Verschelde 7 years ago
parent
commit
98846b39ee
10 changed files with 426 additions and 300 deletions
  1. 13 0
      core/SCsub
  2. 25 0
      core/bind/core_bind.cpp
  3. 5 0
      core/bind/core_bind.h
  4. 75 0
      core/engine.cpp
  5. 5 0
      core/engine.h
  6. 44 0
      doc/classes/Engine.xml
  7. 0 276
      editor/SCsub
  8. 31 22
      editor/editor_about.cpp
  9. 1 1
      editor/editor_about.h
  10. 227 1
      methods.py

+ 13 - 0
core/SCsub

@@ -2,6 +2,8 @@
 
 Import('env')
 
+import methods
+
 env.core_sources = []
 
 
@@ -93,6 +95,17 @@ env.add_source_files(env.core_sources, "*.cpp")
 import make_binders
 env.Command(['method_bind.gen.inc', 'method_bind_ext.gen.inc'], 'make_binders.py', make_binders.run)
 
+# Authors
+env.Depends('#core/authors.gen.h', "../AUTHORS.md")
+env.Command('#core/authors.gen.h', "../AUTHORS.md", methods.make_authors_header)
+
+# Donors
+env.Depends('#core/donors.gen.h', "../DONORS.md")
+env.Command('#core/donors.gen.h', "../DONORS.md", methods.make_donors_header)
+
+# License
+env.Depends('#core/license.gen.h', ["../COPYRIGHT.txt", "../LICENSE.txt"])
+env.Command('#core/license.gen.h', ["../COPYRIGHT.txt", "../LICENSE.txt"], methods.make_license_header)
 
 # Chain load SCsubs
 SConscript('os/SCsub')

+ 25 - 0
core/bind/core_bind.cpp

@@ -2710,6 +2710,26 @@ Dictionary _Engine::get_version_info() const {
 	return Engine::get_singleton()->get_version_info();
 }
 
+Dictionary _Engine::get_author_info() const {
+	return Engine::get_singleton()->get_author_info();
+}
+
+Array _Engine::get_copyright_info() const {
+	return Engine::get_singleton()->get_copyright_info();
+}
+
+Dictionary _Engine::get_donor_info() const {
+	return Engine::get_singleton()->get_donor_info();
+}
+
+Dictionary _Engine::get_license_info() const {
+	return Engine::get_singleton()->get_license_info();
+}
+
+String _Engine::get_license_text() const {
+	return Engine::get_singleton()->get_license_text();
+}
+
 bool _Engine::is_in_physics_frame() const {
 	return Engine::get_singleton()->is_in_physics_frame();
 }
@@ -2752,6 +2772,11 @@ void _Engine::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("get_main_loop"), &_Engine::get_main_loop);
 
 	ClassDB::bind_method(D_METHOD("get_version_info"), &_Engine::get_version_info);
+	ClassDB::bind_method(D_METHOD("get_author_info"), &_Engine::get_author_info);
+	ClassDB::bind_method(D_METHOD("get_copyright_info"), &_Engine::get_copyright_info);
+	ClassDB::bind_method(D_METHOD("get_donor_info"), &_Engine::get_donor_info);
+	ClassDB::bind_method(D_METHOD("get_license_info"), &_Engine::get_license_info);
+	ClassDB::bind_method(D_METHOD("get_license_text"), &_Engine::get_license_text);
 
 	ClassDB::bind_method(D_METHOD("is_in_physics_frame"), &_Engine::is_in_physics_frame);
 

+ 5 - 0
core/bind/core_bind.h

@@ -689,6 +689,11 @@ public:
 	MainLoop *get_main_loop() const;
 
 	Dictionary get_version_info() const;
+	Dictionary get_author_info() const;
+	Array get_copyright_info() const;
+	Dictionary get_donor_info() const;
+	Dictionary get_license_info() const;
+	String get_license_text() const;
 
 	bool is_in_physics_frame() const;
 

+ 75 - 0
core/engine.cpp

@@ -30,6 +30,9 @@
 
 #include "engine.h"
 
+#include "authors.gen.h"
+#include "donors.gen.h"
+#include "license.gen.h"
 #include "version.h"
 #include "version_hash.gen.h"
 
@@ -111,6 +114,78 @@ Dictionary Engine::get_version_info() const {
 	return dict;
 }
 
+static Array array_from_info(const char *const *info_list) {
+	Array arr;
+	for (int i = 0; info_list[i] != NULL; i++) {
+		arr.push_back(info_list[i]);
+	}
+	return arr;
+}
+
+static Array array_from_info_count(const char *const *info_list, int info_count) {
+	Array arr;
+	for (int i = 0; i < info_count; i++) {
+		arr.push_back(info_list[i]);
+	}
+	return arr;
+}
+
+Dictionary Engine::get_author_info() const {
+	Dictionary dict;
+
+	dict["lead_developers"] = array_from_info(AUTHORS_LEAD_DEVELOPERS);
+	dict["project_managers"] = array_from_info(AUTHORS_PROJECT_MANAGERS);
+	dict["founders"] = array_from_info(AUTHORS_FOUNDERS);
+	dict["developers"] = array_from_info(AUTHORS_DEVELOPERS);
+
+	return dict;
+}
+
+Array Engine::get_copyright_info() const {
+	Array components;
+	for (int component_index = 0; component_index < COPYRIGHT_INFO_COUNT; component_index++) {
+		const ComponentCopyright &cp_info = COPYRIGHT_INFO[component_index];
+		Dictionary component_dict;
+		component_dict["name"] = cp_info.name;
+		Array parts;
+		for (int i = 0; i < cp_info.part_count; i++) {
+			const ComponentCopyrightPart &cp_part = cp_info.parts[i];
+			Dictionary part_dict;
+			part_dict["files"] = array_from_info_count(cp_part.files, cp_part.file_count);
+			part_dict["copyright"] = array_from_info_count(cp_part.copyright_statements, cp_part.copyright_count);
+			part_dict["license"] = cp_part.license;
+			parts.push_back(part_dict);
+		}
+		component_dict["parts"] = parts;
+
+		components.push_back(component_dict);
+	}
+	return components;
+}
+
+Dictionary Engine::get_donor_info() const {
+	Dictionary donors;
+	donors["platinum_sponsors"] = array_from_info(DONORS_SPONSOR_PLAT);
+	donors["gold_sponsors"] = array_from_info(DONORS_SPONSOR_GOLD);
+	donors["mini_sponsors"] = array_from_info(DONORS_SPONSOR_MINI);
+	donors["gold_donors"] = array_from_info(DONORS_GOLD);
+	donors["silver_donors"] = array_from_info(DONORS_SILVER);
+	donors["bronze_donors"] = array_from_info(DONORS_BRONZE);
+	return donors;
+}
+
+Dictionary Engine::get_license_info() const {
+	Dictionary licenses;
+	for (int i = 0; i < LICENSE_COUNT; i++) {
+		licenses[LICENSE_NAMES[i]] = LICENSE_BODIES[i];
+	}
+	return licenses;
+}
+
+String Engine::get_license_text() const {
+	return String(GODOT_LICENSE_TEXT);
+}
+
 void Engine::add_singleton(const Singleton &p_singleton) {
 
 	singletons.push_back(p_singleton);

+ 5 - 0
core/engine.h

@@ -118,6 +118,11 @@ public:
 #endif
 
 	Dictionary get_version_info() const;
+	Dictionary get_author_info() const;
+	Array get_copyright_info() const;
+	Dictionary get_donor_info() const;
+	Dictionary get_license_info() const;
+	String get_license_text() const;
 
 	Engine();
 };

+ 44 - 0
doc/classes/Engine.xml

@@ -11,6 +11,36 @@
 	<demos>
 	</demos>
 	<methods>
+		<method name="get_author_info" qualifiers="const">
+			<return type="Dictionary">
+			</return>
+			<description>
+				Returns engine author information in a Dictionary.
+
+				"lead_developers" - Array of Strings, lead developer names
+				"founders" - Array of Strings, founder names
+				"project_managers" - Array of Strings, project manager names
+				"developers" - Array of Strings, developer names
+			</description>
+		</method>
+		<method name="get_copyright_info" qualifiers="const">
+			<return type="Array">
+			</return>
+			<description>
+				Returns an Array of copyright information Dictionaries.
+
+				"name" - String, component name
+				"parts" - Array of Dictionaries {"files", "copyright", "license"} describing subsections of the component
+			</description>
+		</method>
+		<method name="get_donor_info" qualifiers="const">
+			<return type="Dictionary">
+			</return>
+			<description>
+				Returns a Dictionary of Arrays of donor names.
+				{"platinum_sponsors", "gold_sponsors", "mini_sponsors", "gold_donors", "silver_donors", "bronze_donors"}
+			</description>
+		</method>
 		<method name="get_frames_drawn">
 			<return type="int">
 			</return>
@@ -25,6 +55,20 @@
 				Returns the frames per second of the running game.
 			</description>
 		</method>
+		<method name="get_license_info" qualifiers="const">
+			<return type="Dictionary">
+			</return>
+			<description>
+				Returns Dictionary of licenses used by Godot and included third party components.
+			</description>
+		</method>
+		<method name="get_license_text" qualifiers="const">
+			<return type="String">
+			</return>
+			<description>
+				Returns Godot license text.
+			</description>
+		</method>
 		<method name="get_main_loop" qualifiers="const">
 			<return type="MainLoop">
 			</return>

+ 0 - 276
editor/SCsub

@@ -7,7 +7,6 @@ import os
 import os.path
 from compat import encode_utf8, byte_to_str, open_utf8, escape_string
 
-
 def make_certs_header(target, source, env):
 
     src = source[0].srcnode().abspath
@@ -147,268 +146,6 @@ def make_translations_header(target, source, env):
 
     g.close()
 
-
-def make_authors_header(target, source, env):
-
-    sections = ["Project Founders", "Lead Developer", "Project Manager", "Developers"]
-    sections_id = ["dev_founders", "dev_lead", "dev_manager", "dev_names"]
-
-    src = source[0].srcnode().abspath
-    dst = target[0].srcnode().abspath
-    f = open_utf8(src, "r")
-    g = open_utf8(dst, "w")
-
-    g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n")
-    g.write("#ifndef _EDITOR_AUTHORS_H\n")
-    g.write("#define _EDITOR_AUTHORS_H\n")
-
-    current_section = ""
-    reading = False
-
-    def close_section():
-        g.write("\t0\n")
-        g.write("};\n")
-
-    for line in f:
-        if reading:
-            if line.startswith("    "):
-                g.write("\t\"" + escape_string(line.strip()) + "\",\n")
-                continue
-        if line.startswith("## "):
-            if reading:
-                close_section()
-                reading = False
-            for i in range(len(sections)):
-                if line.strip().endswith(sections[i]):
-                    current_section = escape_string(sections_id[i])
-                    reading = True
-                    g.write("static const char *" + current_section + "[] = {\n")
-                    break
-
-    if reading:
-        close_section()
-
-    g.write("#endif\n")
-
-    g.close()
-    f.close()
-
-def make_donors_header(target, source, env):
-
-    sections = ["Platinum sponsors", "Gold sponsors", "Mini sponsors", "Gold donors", "Silver donors", "Bronze donors"]
-    sections_id = ["donor_s_plat", "donor_s_gold", "donor_s_mini", "donor_gold", "donor_silver", "donor_bronze"]
-
-    src = source[0].srcnode().abspath
-    dst = target[0].srcnode().abspath
-    f = open_utf8(src, "r")
-    g = open_utf8(dst, "w")
-
-    g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n")
-    g.write("#ifndef _EDITOR_DONORS_H\n")
-    g.write("#define _EDITOR_DONORS_H\n")
-
-    current_section = ""
-    reading = False
-
-    def close_section():
-        g.write("\t0\n")
-        g.write("};\n")
-
-    for line in f:
-        if reading >= 0:
-            if line.startswith("    "):
-                g.write("\t\"" + escape_string(line.strip()) + "\",\n")
-                continue
-        if line.startswith("## "):
-            if reading:
-                close_section()
-                reading = False
-            for i in range(len(sections)):
-                if line.strip().endswith(sections[i]):
-                    current_section = escape_string(sections_id[i])
-                    reading = True
-                    g.write("static const char *" + current_section + "[] = {\n")
-                    break
-
-    if reading:
-        close_section()
-
-    g.write("#endif\n")
-
-    g.close()
-    f.close()
-
-
-def make_license_header(target, source, env):
-
-    src_copyright = source[0].srcnode().abspath
-    src_license = source[1].srcnode().abspath
-    dst = target[0].srcnode().abspath
-    f = open_utf8(src_license, "r")
-    fc = open_utf8(src_copyright, "r")
-    g = open_utf8(dst, "w")
-
-    g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n")
-    g.write("#ifndef _EDITOR_LICENSE_H\n")
-    g.write("#define _EDITOR_LICENSE_H\n")
-    g.write("static const char *about_license =")
-
-    for line in f:
-        escaped_string = escape_string(line.strip())
-        g.write("\n\t\"" + escaped_string + "\\n\"")
-
-    g.write(";\n")
-
-    tp_current = 0
-    tp_file = ""
-    tp_comment = ""
-    tp_copyright = ""
-    tp_license = ""
-
-    tp_licensename = ""
-    tp_licensebody = ""
-
-    tp = []
-    tp_licensetext = []
-    for line in fc:
-        if line.startswith("#"):
-            continue
-
-        if line.startswith("Files:"):
-            tp_file = line[6:].strip()
-            tp_current = 1
-        elif line.startswith("Comment:"):
-            tp_comment = line[8:].strip()
-            tp_current = 2
-        elif line.startswith("Copyright:"):
-            tp_copyright = line[10:].strip()
-            tp_current = 3
-        elif line.startswith("License:"):
-            if tp_current != 0:
-                tp_license = line[8:].strip()
-                tp_current = 4
-            else:
-                tp_licensename = line[8:].strip()
-                tp_current = 5
-        elif line.startswith(" "):
-            if tp_current == 1:
-                tp_file += "\n" + line.strip()
-            elif tp_current == 3:
-                tp_copyright += "\n" + line.strip()
-            elif tp_current == 5:
-                if line.strip() == ".":
-                    tp_licensebody += "\n"
-                else:
-                    tp_licensebody += line[1:]
-        else:
-            if tp_current != 0:
-                if tp_current == 5:
-                    tp_licensetext.append([tp_licensename, tp_licensebody])
-
-                    tp_licensename = ""
-                    tp_licensebody = ""
-                else:
-                    added = False
-                    for i in tp:
-                        if i[0] == tp_comment:
-                            i[1].append([tp_file, tp_copyright, tp_license])
-                            added = True
-                            break
-                    if not added:
-                        tp.append([tp_comment,[[tp_file, tp_copyright, tp_license]]])
-
-                    tp_file = []
-                    tp_comment = ""
-                    tp_copyright = []
-                    tp_license = ""
-                tp_current = 0
-
-    tp_licensetext.append([tp_licensename, tp_licensebody])
-
-    about_thirdparty = ""
-    about_tp_copyright_count = ""
-    about_tp_license = ""
-    about_tp_copyright = ""
-    about_tp_file = ""
-
-    for i in tp:
-        about_thirdparty += "\t\"" + i[0] + "\",\n"
-        about_tp_copyright_count += str(len(i[1])) + ", "
-        for j in i[1]:
-            file_body = ""
-            copyright_body = ""
-            for k in j[0].split("\n"):
-                if file_body != "":
-                    file_body += "\\n\"\n"
-                escaped_string = escape_string(k.strip())
-                file_body += "\t\"" + escaped_string
-            for k in j[1].split("\n"):
-                if copyright_body != "":
-                    copyright_body += "\\n\"\n"
-                escaped_string = escape_string(k.strip())
-                copyright_body += "\t\"" + escaped_string
-
-            about_tp_file += "\t" + file_body + "\",\n"
-            about_tp_copyright += "\t" + copyright_body + "\",\n"
-            about_tp_license += "\t\"" + j[2] + "\",\n"
-
-    about_license_name = ""
-    about_license_body = ""
-
-    for i in tp_licensetext:
-        body = ""
-        for j in i[1].split("\n"):
-            if body != "":
-                body += "\\n\"\n"
-            escaped_string = escape_string(j.strip())
-            body += "\t\"" + escaped_string
-
-        about_license_name += "\t\"" + i[0] + "\",\n"
-        about_license_body += "\t" + body + "\",\n"
-
-    g.write("static const char *about_thirdparty[] = {\n")
-    g.write(about_thirdparty)
-    g.write("\t0\n")
-    g.write("};\n")
-    g.write("#define THIRDPARTY_COUNT " + str(len(tp)) + "\n")
-
-    g.write("static const int about_tp_copyright_count[] = {\n\t")
-    g.write(about_tp_copyright_count)
-    g.write("0\n};\n")
-
-    g.write("static const char *about_tp_file[] = {\n")
-    g.write(about_tp_file)
-    g.write("\t0\n")
-    g.write("};\n")
-
-    g.write("static const char *about_tp_copyright[] = {\n")
-    g.write(about_tp_copyright)
-    g.write("\t0\n")
-    g.write("};\n")
-
-    g.write("static const char *about_tp_license[] = {\n")
-    g.write(about_tp_license)
-    g.write("\t0\n")
-    g.write("};\n")
-
-    g.write("static const char *about_license_name[] = {\n")
-    g.write(about_license_name)
-    g.write("\t0\n")
-    g.write("};\n")
-    g.write("#define LICENSE_COUNT " + str(len(tp_licensetext)) + "\n")
-
-    g.write("static const char *about_license_body[] = {\n")
-    g.write(about_license_body)
-    g.write("\t0\n")
-    g.write("};\n")
-
-    g.write("#endif\n")
-
-    g.close()
-    fc.close()
-    f.close()
-
-
 def _make_doc_data_class_path(to_path):
     g = open_utf8(os.path.join(to_path,"doc_data_class_path.gen.h"), "w")
     g.write("static const int _doc_data_class_path_count = " + str(len(env.doc_class_path)) + ";\n")
@@ -474,19 +211,6 @@ if env['tools']:
     env.Depends('#editor/builtin_fonts.gen.h', flist)
     env.Command('#editor/builtin_fonts.gen.h', flist, make_fonts_header)
 
-    # Authors
-    env.Depends('#editor/authors.gen.h', "../AUTHORS.md")
-    env.Command('#editor/authors.gen.h', "../AUTHORS.md", make_authors_header)
-
-    # Donors
-    env.Depends('#editor/donors.gen.h', "../DONORS.md")
-    env.Command('#editor/donors.gen.h', "../DONORS.md", make_donors_header)
-
-    # License
-    env.Depends('#editor/license.gen.h', ["../COPYRIGHT.txt", "../LICENSE.txt"])
-    env.Command('#editor/license.gen.h', ["../COPYRIGHT.txt", "../LICENSE.txt"], make_license_header)
-
-
     env.add_source_files(env.editor_sources, "*.cpp")
     env.add_source_files(env.editor_sources, ["#thirdparty/misc/clipper.cpp"])
 

+ 31 - 22
editor/editor_about.cpp

@@ -31,11 +31,11 @@
 #include "editor_about.h"
 #include "editor_node.h"
 
-#include "authors.gen.h"
-#include "donors.gen.h"
-#include "license.gen.h"
-#include "version.h"
-#include "version_hash.gen.h"
+#include "core/authors.gen.h"
+#include "core/donors.gen.h"
+#include "core/license.gen.h"
+#include "core/version.h"
+#include "core/version_hash.gen.h"
 
 void EditorAbout::_notification(int p_what) {
 
@@ -69,7 +69,7 @@ TextureRect *EditorAbout::get_logo() const {
 	return _logo;
 }
 
-ScrollContainer *EditorAbout::_populate_list(const String &p_name, const List<String> &p_sections, const char **p_src[], const int p_flag_single_column) {
+ScrollContainer *EditorAbout::_populate_list(const String &p_name, const List<String> &p_sections, const char *const *const p_src[], const int p_flag_single_column) {
 
 	ScrollContainer *sc = memnew(ScrollContainer);
 	sc->set_name(p_name);
@@ -82,7 +82,7 @@ ScrollContainer *EditorAbout::_populate_list(const String &p_name, const List<St
 	for (int i = 0; i < p_sections.size(); i++) {
 
 		bool single_column = p_flag_single_column & 1 << i;
-		const char **names_ptr = p_src[i];
+		const char *const *names_ptr = p_src[i];
 		if (*names_ptr) {
 
 			Label *lbl = memnew(Label);
@@ -151,7 +151,8 @@ EditorAbout::EditorAbout() {
 	dev_sections.push_back(TTR("Lead Developer"));
 	dev_sections.push_back(TTR("Project Manager ")); // " " appended to distinguish between 'project supervisor' and 'project list'
 	dev_sections.push_back(TTR("Developers"));
-	const char **dev_src[] = { dev_founders, dev_lead, dev_manager, dev_names };
+	const char *const *dev_src[] = { AUTHORS_FOUNDERS, AUTHORS_LEAD_DEVELOPERS,
+		AUTHORS_PROJECT_MANAGERS, AUTHORS_DEVELOPERS };
 	tc->add_child(_populate_list(TTR("Authors"), dev_sections, dev_src, 1));
 
 	// Donors
@@ -163,7 +164,8 @@ EditorAbout::EditorAbout() {
 	donor_sections.push_back(TTR("Gold Donors"));
 	donor_sections.push_back(TTR("Silver Donors"));
 	donor_sections.push_back(TTR("Bronze Donors"));
-	const char **donor_src[] = { donor_s_plat, donor_s_gold, donor_s_mini, donor_gold, donor_silver, donor_bronze };
+	const char *const *donor_src[] = { DONORS_SPONSOR_PLAT, DONORS_SPONSOR_GOLD,
+		DONORS_SPONSOR_MINI, DONORS_GOLD, DONORS_SILVER, DONORS_BRONZE };
 	tc->add_child(_populate_list(TTR("Donors"), donor_sections, donor_src, 3));
 
 	// License
@@ -172,7 +174,7 @@ EditorAbout::EditorAbout() {
 	_license_text->set_name(TTR("License"));
 	_license_text->set_h_size_flags(Control::SIZE_EXPAND_FILL);
 	_license_text->set_v_size_flags(Control::SIZE_EXPAND_FILL);
-	_license_text->set_text(String::utf8(about_license));
+	_license_text->set_text(String::utf8(GODOT_LICENSE_TEXT));
 	tc->add_child(_license_text);
 
 	// Thirdparty License
@@ -207,20 +209,27 @@ EditorAbout::EditorAbout() {
 	tpl_ti_lc->set_selectable(0, false);
 	int read_idx = 0;
 	String long_text = "";
-	for (int i = 0; i < THIRDPARTY_COUNT; i++) {
+	for (int component_index = 0; component_index < COPYRIGHT_INFO_COUNT; component_index++) {
 
+		const ComponentCopyright &component = COPYRIGHT_INFO[component_index];
 		TreeItem *ti = _tpl_tree->create_item(tpl_ti_tp);
-		String thirdparty = String(about_thirdparty[i]);
-		ti->set_text(0, thirdparty);
-		String text = thirdparty + "\n";
-		long_text += "- " + thirdparty + "\n\n";
-		for (int j = 0; j < about_tp_copyright_count[i]; j++) {
-
-			text += "\n    Files:\n        " + String(about_tp_file[read_idx]).replace("\n", "\n        ") + "\n";
-			String copyright = String::utf8("    \xc2\xa9 ") + String::utf8(about_tp_copyright[read_idx]).replace("\n", String::utf8("\n    \xc2\xa9 "));
+		String component_name = component.name;
+		ti->set_text(0, component_name);
+		String text = component_name + "\n";
+		long_text += "- " + component_name + "\n";
+		for (int part_index = 0; part_index < component.part_count; part_index++) {
+			const ComponentCopyrightPart &part = component.parts[part_index];
+			text += "\n    Files:";
+			for (int file_num = 0; file_num < part.file_count; file_num++) {
+				text += "\n        " + String(part.files[file_num]);
+			}
+			String copyright;
+			for (int copyright_index = 0; copyright_index < part.copyright_count; copyright_index++) {
+				copyright += String::utf8("\n    \xc2\xa9 ") + String::utf8(part.copyright_statements[copyright_index]);
+			}
 			text += copyright;
 			long_text += copyright;
-			String license = "\n    License: " + String(about_tp_license[read_idx]) + "\n";
+			String license = "\n    License: " + String(part.license) + "\n";
 			text += license;
 			long_text += license + "\n";
 			read_idx++;
@@ -230,10 +239,10 @@ EditorAbout::EditorAbout() {
 	for (int i = 0; i < LICENSE_COUNT; i++) {
 
 		TreeItem *ti = _tpl_tree->create_item(tpl_ti_lc);
-		String licensename = String(about_license_name[i]);
+		String licensename = String(LICENSE_NAMES[i]);
 		ti->set_text(0, licensename);
 		long_text += "- " + licensename + "\n\n";
-		String licensebody = String(about_license_body[i]);
+		String licensebody = String(LICENSE_BODIES[i]);
 		ti->set_metadata(0, licensebody);
 		long_text += "    " + licensebody.replace("\n", "\n    ") + "\n\n";
 	}

+ 1 - 1
editor/editor_about.h

@@ -53,7 +53,7 @@ class EditorAbout : public AcceptDialog {
 
 private:
 	void _license_tree_selected();
-	ScrollContainer *_populate_list(const String &p_name, const List<String> &p_sections, const char **p_src[], const int p_flag_single_column = 0);
+	ScrollContainer *_populate_list(const String &p_name, const List<String> &p_sections, const char *const *const p_src[], const int p_flag_single_column = 0);
 
 	Tree *_tpl_tree;
 	RichTextLabel *_license_text;

+ 227 - 1
methods.py

@@ -1,5 +1,5 @@
 import os
-from compat import iteritems
+from compat import iteritems, open_utf8, escape_string
 
 
 def add_source_files(self, sources, filetype, lib_env=None, shared=False):
@@ -515,6 +515,232 @@ def build_gles2_headers(target, source, env):
     for x in source:
         build_legacygl_header(str(x), include="drivers/gles2/shader_gles2.h", class_suffix="GLES2", output_attribs=True, gles2=True)
 
+def make_authors_header(target, source, env):
+
+    sections = ["Project Founders", "Lead Developer", "Project Manager", "Developers"]
+    sections_id = ["AUTHORS_FOUNDERS", "AUTHORS_LEAD_DEVELOPERS", "AUTHORS_PROJECT_MANAGERS", "AUTHORS_DEVELOPERS"]
+
+    src = source[0].srcnode().abspath
+    dst = target[0].srcnode().abspath
+    f = open_utf8(src, "r")
+    g = open_utf8(dst, "w")
+
+    g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n")
+    g.write("#ifndef _EDITOR_AUTHORS_H\n")
+    g.write("#define _EDITOR_AUTHORS_H\n")
+
+    current_section = ""
+    reading = False
+
+    def close_section():
+        g.write("\t0\n")
+        g.write("};\n")
+
+    for line in f:
+        if reading:
+            if line.startswith("    "):
+                g.write("\t\"" + escape_string(line.strip()) + "\",\n")
+                continue
+        if line.startswith("## "):
+            if reading:
+                close_section()
+                reading = False
+            for i in range(len(sections)):
+                if line.strip().endswith(sections[i]):
+                    current_section = escape_string(sections_id[i])
+                    reading = True
+                    g.write("const char *const " + current_section + "[] = {\n")
+                    break
+
+    if reading:
+        close_section()
+
+    g.write("#endif\n")
+
+    g.close()
+    f.close()
+
+def make_donors_header(target, source, env):
+
+    sections = ["Platinum sponsors", "Gold sponsors", "Mini sponsors",
+            "Gold donors", "Silver donors", "Bronze donors"]
+    sections_id = ["DONORS_SPONSOR_PLAT", "DONORS_SPONSOR_GOLD", "DONORS_SPONSOR_MINI",
+            "DONORS_GOLD", "DONORS_SILVER", "DONORS_BRONZE"]
+
+    src = source[0].srcnode().abspath
+    dst = target[0].srcnode().abspath
+    f = open_utf8(src, "r")
+    g = open_utf8(dst, "w")
+
+    g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n")
+    g.write("#ifndef _EDITOR_DONORS_H\n")
+    g.write("#define _EDITOR_DONORS_H\n")
+
+    current_section = ""
+    reading = False
+
+    def close_section():
+        g.write("\t0\n")
+        g.write("};\n")
+
+    for line in f:
+        if reading >= 0:
+            if line.startswith("    "):
+                g.write("\t\"" + escape_string(line.strip()) + "\",\n")
+                continue
+        if line.startswith("## "):
+            if reading:
+                close_section()
+                reading = False
+            for i in range(len(sections)):
+                if line.strip().endswith(sections[i]):
+                    current_section = escape_string(sections_id[i])
+                    reading = True
+                    g.write("const char *const " + current_section + "[] = {\n")
+                    break
+
+    if reading:
+        close_section()
+
+    g.write("#endif\n")
+
+    g.close()
+    f.close()
+
+
+def make_license_header(target, source, env):
+    src_copyright = source[0].srcnode().abspath
+    src_license = source[1].srcnode().abspath
+    dst = target[0].srcnode().abspath
+
+    class LicenseReader:
+        def __init__(self, license_file):
+            self._license_file = license_file
+            self.line_num = 0
+            self.current = self.next_line()
+
+        def next_line(self):
+            line = self._license_file.readline()
+            self.line_num += 1
+            while line.startswith("#"):
+                line = self._license_file.readline()
+                self.line_num += 1
+            self.current = line
+            return line
+
+        def next_tag(self):
+            if not ':' in self.current:
+                return ('',[])
+            tag, line = self.current.split(":", 1)
+            lines = [line.strip()]
+            while self.next_line() and self.current.startswith(" "):
+                lines.append(self.current.strip())
+            return (tag, lines)
+
+    from collections import OrderedDict
+    projects = OrderedDict()
+    license_list = []
+
+    with open_utf8(src_copyright, "r") as copyright_file:
+        reader = LicenseReader(copyright_file)
+        part = {}
+        while reader.current:
+            tag, content = reader.next_tag()
+            if tag in ("Files", "Copyright", "License"):
+                part[tag] = content[:]
+            elif tag == "Comment":
+                # attach part to named project
+                projects[content[0]] = projects.get(content[0], []) + [part]
+
+            if not tag or not reader.current:
+                # end of a paragraph start a new part
+                if "License" in part and not "Files" in part:
+                    # no Files tag in this one, so assume standalone license
+                    license_list.append(part["License"])
+                part = {}
+                reader.next_line()
+
+    data_list = []
+    for project in projects.itervalues():
+        for part in project:
+            part["file_index"] = len(data_list)
+            data_list += part["Files"]
+            part["copyright_index"] = len(data_list)
+            data_list += part["Copyright"]
+
+    with open_utf8(dst, "w") as f:
+
+        f.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n")
+        f.write("#ifndef _EDITOR_LICENSE_H\n")
+        f.write("#define _EDITOR_LICENSE_H\n")
+        f.write("const char *const GODOT_LICENSE_TEXT =")
+
+        with open_utf8(src_license, "r") as license_file:
+            for line in license_file:
+                escaped_string = escape_string(line.strip())
+                f.write("\n\t\t\"" + escaped_string + "\\n\"")
+        f.write(";\n\n")
+
+        f.write("struct ComponentCopyrightPart {\n"
+                "\tconst char *license;\n"
+                "\tconst char *const *files;\n"
+                "\tconst char *const *copyright_statements;\n"
+                "\tint file_count;\n"
+                "\tint copyright_count;\n"
+                "};\n\n")
+
+        f.write("struct ComponentCopyright {\n"
+                "\tconst char *name;\n"
+                "\tconst ComponentCopyrightPart *parts;\n"
+                "\tint part_count;\n"
+                "};\n\n")
+
+        f.write("const char *const COPYRIGHT_INFO_DATA[] = {\n")
+        for line in data_list:
+            f.write("\t\"" + escape_string(line) + "\",\n")
+        f.write("};\n\n")
+
+        f.write("const ComponentCopyrightPart COPYRIGHT_PROJECT_PARTS[] = {\n")
+        part_index = 0
+        part_indexes = {}
+        for project_name, project in projects.iteritems():
+            part_indexes[project_name] = part_index
+            for part in project:
+                f.write("\t{ \"" + escape_string(part["License"][0]) + "\", "
+                        + "&COPYRIGHT_INFO_DATA[" + str(part["file_index"]) + "], "
+                        + "&COPYRIGHT_INFO_DATA[" + str(part["copyright_index"]) + "], "
+                        + str(len(part["Files"])) + ", "
+                        + str(len(part["Copyright"])) + " },\n")
+                part_index += 1
+        f.write("};\n\n")
+
+        f.write("const int COPYRIGHT_INFO_COUNT = " + str(len(projects)) + ";\n")
+
+        f.write("const ComponentCopyright COPYRIGHT_INFO[] = {\n")
+        for project_name, project in projects.iteritems():
+            f.write("\t{ \"" + escape_string(project_name) + "\", "
+                    + "&COPYRIGHT_PROJECT_PARTS[" + str(part_indexes[project_name]) + "], "
+                    + str(len(project)) + " },\n")
+        f.write("};\n\n")
+
+        f.write("const int LICENSE_COUNT = " + str(len(license_list)) + ";\n")
+
+        f.write("const char *const LICENSE_NAMES[] = {\n")
+        for l in license_list:
+            f.write("\t\"" + escape_string(l[0]) + "\",\n")
+        f.write("};\n\n")
+
+        f.write("const char *const LICENSE_BODIES[] = {\n\n")
+        for l in license_list:
+            for line in l[1:]:
+                if line == ".":
+                    f.write("\t\"\\n\"\n")
+                else:
+                    f.write("\t\"" + escape_string(line) + "\\n\"\n")
+            f.write("\t\"\",\n\n")
+        f.write("};\n\n")
+
+        f.write("#endif\n")
 def add_module_version_string(self,s):
     self.module_version_string += "." + s