Skip to content

Commit 6cd052e

Browse files
committed
Add support to pack GL features to Debian packages
Signed-off-by: Tobias Wolf <wolf@b1-systems.de> On-behalf-of: SAP <tobias.wolf@sap.com>
1 parent ca8aaa9 commit 6cd052e

28 files changed

+1168
-2
lines changed

src/gardenlinux/apt/__init__.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,31 @@
44
APT module
55
"""
66

7+
from .changelog_file import ChangelogFile
8+
from .control_file import ControlFile
9+
from .copyright_file import CopyrightFile
710
from .debsource import Debsrc, DebsrcFile
11+
from .docs_file import DocsFile
12+
from .install_file import InstallFile
813
from .package_repo_info import GardenLinuxRepo
14+
from .preinst_file import PreinstFile
15+
from .prerm_file import PrermFile
16+
from .postinst_file import PostinstFile
17+
from .postrm_file import PostrmFile
18+
from .rules_file import RulesFile
919

10-
__all__ = ["Debsrc", "DebsrcFile", "GardenLinuxRepo"]
20+
__all__ = [
21+
"ChangelogFile",
22+
"ControlFile",
23+
"CopyrightFile",
24+
"Debsrc",
25+
"DebsrcFile",
26+
"DocsFile",
27+
"GardenLinuxRepo",
28+
"InstallFile",
29+
"PreinstFile",
30+
"PrermFile",
31+
"PostinstFile",
32+
"PostrmFile",
33+
"RulesFile",
34+
]
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from time import gmtime, strftime
4+
import textwrap
5+
6+
from ..constants import GL_AUTHORS_EMAIL, GL_AUTHORS_NAME
7+
from .debian_file_mixin import DebianFileMixin
8+
9+
10+
class ChangelogFile(DebianFileMixin):
11+
def __init__(self, package_name, maintainer=None, maintainer_email=None):
12+
DebianFileMixin.__init__(self)
13+
14+
self._entries = []
15+
self._package_name = package_name
16+
17+
if maintainer is None:
18+
maintainer = GL_AUTHORS_NAME
19+
if maintainer_email is None:
20+
maintainer_email = GL_AUTHORS_EMAIL
21+
22+
self._maintainer = maintainer
23+
self._maintainer_email = maintainer_email
24+
25+
@property
26+
def content(self):
27+
content = ""
28+
29+
for entry in self._entries:
30+
content += f"{entry}\n\n"
31+
32+
return content.strip() + "\n"
33+
34+
def add_entry(
35+
self,
36+
package_version,
37+
package_timestamp,
38+
changes,
39+
maintainer=None,
40+
maintainer_email=None,
41+
):
42+
if maintainer is None:
43+
maintainer = self._maintainer
44+
if maintainer_email is None:
45+
maintainer_email = self._maintainer_email
46+
47+
changes = textwrap.indent(changes.strip(), " ")
48+
49+
package_timestamp_string = strftime(
50+
"%a, %d %b %Y %H:%M:%S +0000", gmtime(package_timestamp)
51+
)
52+
53+
content = f"""
54+
{self._package_name} ({package_version}) universal; urgency=low
55+
56+
{changes}
57+
58+
-- {maintainer} <{maintainer_email}> {package_timestamp_string}
59+
"""
60+
61+
self._entries.append(content.strip())
62+
63+
def generate(self, target_dir):
64+
self._generate(target_dir, "changelog", self.content)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# -*- coding: utf-8 -*-
2+
3+
4+
class CodeFileMixin(object):
5+
def __init__(self, executable=None, header_code=None, footer_code=None):
6+
self._code = []
7+
self._executable = executable
8+
self._footer_code = footer_code
9+
self._header_code = header_code
10+
11+
@property
12+
def content(self):
13+
content = ""
14+
15+
if len(self._code) > 0:
16+
if self._executable is not None:
17+
content += f"#!{self._executable}\n\n"
18+
19+
if self._header_code is not None:
20+
content += self._header_code + "\n"
21+
22+
for code in self._code:
23+
content += f"\n{code}\n"
24+
25+
if self._footer_code is not None:
26+
content += "\n" + self._footer_code
27+
28+
return content.strip() + "\n"
29+
30+
@property
31+
def empty(self):
32+
return len(self._code) < 1
33+
34+
def add_code(self, content):
35+
self._code.append(content.strip())
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from os import PathLike
4+
from pathlib import Path
5+
import textwrap
6+
7+
from ..constants import GL_AUTHORS_EMAIL, GL_AUTHORS_NAME, GL_HOME_URL
8+
from .debian_file_mixin import DebianFileMixin
9+
10+
11+
class ControlFile(dict, DebianFileMixin):
12+
def __init__(self, package_name, *args, **kwargs):
13+
dict.__init__(self, *args, **kwargs)
14+
DebianFileMixin.__init__(self)
15+
16+
self._conflicts = []
17+
self._breaking_packages = []
18+
self._dependencies = []
19+
self["package_name"] = package_name
20+
self._packages = []
21+
22+
@property
23+
def content(self):
24+
homepage = self.get("homepage", GL_HOME_URL)
25+
maintainer = self.get("maintainer", GL_AUTHORS_NAME)
26+
maintainer_email = self.get("maintainer_email", GL_AUTHORS_EMAIL)
27+
28+
content = f"""
29+
Source: {self["package_name"]}
30+
Standards-Version: 4.0.0
31+
Section: universe
32+
Priority: optional
33+
Maintainer: {maintainer} <{maintainer_email}>
34+
Build-Depends: debhelper-compat (=12)
35+
Homepage: {homepage}
36+
""".strip()
37+
38+
for package in self._packages:
39+
content += f"\n\n{package}"
40+
41+
if len(self._dependencies) > 0:
42+
dependencies = textwrap.indent(", ".join(self._dependencies), " ")
43+
content += f"\nDepends:{dependencies}"
44+
45+
if len(self._conflicts) > 0:
46+
dependencies = textwrap.indent(", ".join(self._conflicts), " ")
47+
content += f"\nConflicts:{dependencies}"
48+
49+
if len(self._breaking_packages) > 0:
50+
dependencies = textwrap.indent(", ".join(self._breaking_packages), " ")
51+
content += f"\nBreaks:{dependencies}"
52+
53+
content += "\n"
54+
55+
return content
56+
57+
def add_breaking_package(self, package_name):
58+
if (
59+
package_name not in self._breaking_packages
60+
and package_name not in self._conflicts
61+
):
62+
self._breaking_packages.append(package_name)
63+
64+
def add_conflict(self, package_name):
65+
if (
66+
package_name not in self._breaking_packages
67+
and package_name not in self._conflicts
68+
):
69+
self._conflicts.append(package_name)
70+
71+
def add_dependency(self, package_name):
72+
if package_name not in self._dependencies:
73+
self._dependencies.append(package_name)
74+
75+
def add_package(self, package_name, description, architecture=None):
76+
if architecture is None:
77+
architecture = "any"
78+
79+
description = textwrap.indent(description.strip(), " ")
80+
81+
content = f"""
82+
Package: {package_name}
83+
Architecture: {architecture}
84+
Description:{description}
85+
"""
86+
87+
self._packages.append(content.strip())
88+
89+
def generate(self, target_dir):
90+
if not isinstance(target_dir, PathLike):
91+
target_dir = Path(target_dir)
92+
93+
if not target_dir.is_dir():
94+
raise ValueError("Target directory given is invalid")
95+
96+
source_dir_path = target_dir.joinpath("source")
97+
format_file_path = source_dir_path.joinpath("format")
98+
99+
if format_file_path.is_file():
100+
raise RuntimeError(
101+
"Target directory already contains a 'source/format' file"
102+
)
103+
104+
if not source_dir_path.is_dir():
105+
source_dir_path.mkdir()
106+
107+
self._generate(target_dir, "control", self.content)
108+
self._generate(source_dir_path, "format", "3.0 (native)")
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from collections import OrderedDict
4+
import textwrap
5+
6+
from ..constants import GL_AUTHORS_EMAIL, GL_AUTHORS_NAME, GL_REPOSITORY_URL
7+
from .debian_file_mixin import DebianFileMixin
8+
9+
10+
class CopyrightFile(dict, DebianFileMixin):
11+
def __init__(self, package_name, *args, **kwargs):
12+
dict.__init__(self, *args, **kwargs)
13+
DebianFileMixin.__init__(self)
14+
15+
self["package_name"] = package_name
16+
17+
self._licensed_files_entries = OrderedDict()
18+
self._license_text_entries = {}
19+
20+
def add_licensed_files_declaration(
21+
self,
22+
files_definition_string,
23+
copyright_note,
24+
license_id=None,
25+
license_text=None,
26+
):
27+
if files_definition_string in self._licensed_files_entries:
28+
raise ValueError(
29+
"A license has already been declared for the files definition given"
30+
)
31+
32+
if license_id is None and license_text is None:
33+
license_id = "MIT"
34+
35+
license_text = f"""
36+
{copyright_note}
37+
38+
Permission is hereby granted, free of charge, to any person obtaining a copy
39+
of this software and associated documentation files (the "Software"), to
40+
deal in the Software without restriction, including without limitation the
41+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
42+
sell copies of the Software, and to permit persons to whom the Software is
43+
furnished to do so, subject to the following conditions:
44+
45+
The above copyright notice and this permission notice shall be included in
46+
all copies or substantial portions of the Software.
47+
48+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
49+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
50+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
51+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
52+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
53+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
54+
THE SOFTWARE.
55+
"""
56+
57+
if license_id is None or license_text is None:
58+
raise ValueError("Invalid input for license ID or text")
59+
60+
if (
61+
license_id in self._license_text_entries
62+
and self._license_text_entries[license_id] != license_text
63+
):
64+
raise ValueError("Given license text for already used license ID differs")
65+
66+
self._license_text_entries[license_id] = license_text.strip()
67+
68+
files_definition_string = textwrap.indent(files_definition_string.strip(), " ")
69+
copyright_note = textwrap.indent(copyright_note.strip(), " ")
70+
71+
content = f"""
72+
Files:{files_definition_string}
73+
Copyright:{copyright_note}
74+
License: {license_id}
75+
""".strip()
76+
77+
self._licensed_files_entries[files_definition_string] = content
78+
79+
@property
80+
def content(self):
81+
if len(self._licensed_files_entries) < 1:
82+
self.add_licensed_files_declaration("*", self._generate_copyright_note())
83+
84+
maintainer = self.get("maintainer", GL_AUTHORS_NAME)
85+
maintainer_email = self.get("maintainer_email", GL_AUTHORS_EMAIL)
86+
package_name = self["package_name"]
87+
source_url = self.get("source_url", GL_REPOSITORY_URL)
88+
89+
content = f"""
90+
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
91+
Source: {source_url}
92+
Upstream-Name: {package_name}
93+
Upstream-Contact: {maintainer} <{maintainer_email}>
94+
""".strip()
95+
96+
for entry in self._licensed_files_entries.values():
97+
content += f"\n\n{entry}"
98+
99+
for entry_id, entry_text in self._license_text_entries.items():
100+
entry_text = textwrap.indent(entry_text, " ")
101+
content += f"\n\nLicense: {entry_id}\n{entry_text}"
102+
103+
content += "\n"
104+
105+
return content
106+
107+
def generate(self, target_dir):
108+
self._generate(target_dir, "copyright", self.content)
109+
110+
def _generate_copyright_note(self):
111+
return self.get("copyright", f"Copyright (c) {GL_AUTHORS_NAME}")
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from os import PathLike
4+
from pathlib import Path
5+
6+
7+
class DebianFileMixin(object):
8+
def _generate(self, target_dir, file_name, content, chmod_octet=0o644):
9+
if not isinstance(target_dir, PathLike):
10+
target_dir = Path(target_dir)
11+
12+
if not target_dir.is_dir():
13+
raise ValueError("Target directory given is invalid")
14+
15+
file_path_name = target_dir.joinpath(file_name)
16+
17+
if file_path_name.is_file():
18+
raise RuntimeError(
19+
f"Target directory already contains a '{file_path_name}' file"
20+
)
21+
22+
file_path_name.write_text(content)
23+
file_path_name.chmod(chmod_octet)

0 commit comments

Comments
 (0)