0

Python source code

   #!/usr/bin/env python3
"Utility to create .deb files."

import argparse
import io
import json
import os
import subprocess
import sys
import tarfile
import tempfile

def validate_manifest(manifest):
    "Validate that the package manifest makes sense."
    for prop in 'name', 'version', 'files':
        if prop not in manifest:
            sys.exit('Missing mandatory "' + prop + '" property')

    if manifest['arch'] not in ['all', 'arm', 'i686', 'aarch64', 'x86_64']:
        sys.exit('Invalid "arch" - must be one of all/arm/i686/aarch64/x86_64')

def set_default_value(manifest, property_name, default_value):
    "Set a default property value if one does not exist."
    if property_name not in manifest:
        manifest[property_name] = default_value

def setup_default_manifest_values(manifest):
    "Setup default values in a package manifest."
    set_default_value(manifest, 'arch', 'all')
    set_default_value(manifest, 'conflicts', [])
    set_default_value(manifest, 'depends', [])
    set_default_value(manifest, 'description', 'No description')
    set_default_value(manifest, 'maintainer', 'None')
    set_default_value(manifest, 'provides', [])

def write_control_tar(tar_path, manifest):
    "Create a data.tar.xz from the specified manifest."
    contents = 'Package: ' + manifest['name'] + "\n"
    contents += 'Version: ' + manifest['version'] + "\n"
    contents += 'Architecture: ' + manifest['arch'] + "\n"
    contents += 'Maintainer: ' + manifest['maintainer'] + "\n"
    contents += 'Description: ' + manifest['description'] + "\n"

    if 'homepage' in manifest:
        contents += 'Homepage: ' + manifest['homepage'] + "\n"

    if manifest['depends']:
        contents += 'Depends: ' + ','.join(manifest['depends']) + '\n'
    if manifest['provides']:
        contents += 'Provides: ' + ','.join(manifest['provides']) + '\n'
    if manifest['conflicts']:
        contents += 'Conflicts: ' + ','.join(manifest['conflicts']) + '\n'

    control_file = io.BytesIO(contents.encode('utf8'))
    control_file.seek(0, os.SEEK_END)
    file_size = control_file.tell()
    control_file.seek(0)

    info = tarfile.TarInfo(name="control")
    info.size = file_size
    with tarfile.open(tar_path, mode='w:xz') as control_tarfile:
        control_tarfile.addfile(tarinfo=info, fileobj=control_file)

def write_data_tar(tar_path, installation_prefix, package_files):
    "Create a data.tar.xz from the specified package files."
    with tarfile.open(tar_path, mode='w:xz') as data_tarfile:
        for input_file_path in package_files:
            file_stat = os.stat(input_file_path)

            # The tar file path should not start with slash:
            if installation_prefix.startswith('/'):
                installation_prefix = installation_prefix[1:]
            if not installation_prefix.endswith('/'):
                installation_prefix += '/'

            output_file = installation_prefix + package_files[input_file_path]
            info = tarfile.TarInfo(name=output_file)

            info.mode = file_stat.st_mode
            info.mtime = file_stat.st_mtime
            info.size = file_stat.st_size
            with open(input_file_path, 'rb') as input_file:
                data_tarfile.addfile(tarinfo=info, fileobj=input_file)

def create_debfile(debfile_output, directory):
    "Create a debfile from a directory containing control and data tar files."
    subprocess.check_call(['ar', 'r', debfile_output,
                           directory + '/debian-binary',
                           directory + '/control.tar.xz',
                           directory + '/data.tar.xz'
                          ])

DESCRIPTION = """Create a package from a JSON manifest file. Example of manifest:
{
  "name": "mypackage",
  "version": "0.1",
  "arch": "all",
  "maintainer": "@MyGithubNick",
  "description": "This is a hello world package",
  "homepage": "https://example.com",
  "depends": ["python", "vim"],
  "provides": ["vi"],
  "conflicts": ["vim-python-git"],
  "files" : {
    "hello-world.py": "bin/hello-world",
    "hello-world.1": "share/man/man1/hello-world.1"
  }
}
The "maintainer", "description", "homepage", "depends", "provides" and "conflicts" fields are all optional.
The "depends" field should be a comma-separated list of packages that this package depends on. They will be installed automatically when this package is installed using apt.
The "arch" field defaults to "all" (that is, a platform-independent package not containing native code) and can be any of arm/i686/aarch64/x86_64.  Run "uname -m" to find out arch name if building native code inside.
The "files" map is keyed from paths to files to include (relative to the current directory) while the values contains the paths where the files should end up after installation (relative to $PREFIX).
The resulting .deb file can be installed by users with:
  apt install ./package-file.deb
or by hosting it in an apt repository using the repo tool."""

def main(argv):
    "Generate a deb file from a JSON manifest."
    installation_prefix = "/usr/"

    parser = argparse.ArgumentParser(description=DESCRIPTION,
                                     formatter_class=argparse.RawTextHelpFormatter)
    parser.add_argument("manifest", help="a JSON manifest file describing the package")
    parser.add_argument("--prefix", help="set prefix dir (default: " + installation_prefix + ")")
    args = parser.parse_args(argv)

    if args.prefix:
        installation_prefix = str(args.prefix)

    manifest_file_path = args.manifest

    with open(manifest_file_path, 'r') as manifest_file:
        manifest = json.load(manifest_file)

    setup_default_manifest_values(manifest)
    validate_manifest(manifest)

    package_name = manifest['name']
    package_version = manifest['version']
    package_files = manifest['files']

    output_debfile_name = package_name + '_' + package_version + '_' + manifest['arch'] + '.deb'
    print('Building ' + output_debfile_name)

    package_tmp_directory = tempfile.TemporaryDirectory()
    with open(package_tmp_directory.name + '/debian-binary', 'w') as debian_binary:
        debian_binary.write("2.0\n")

    write_control_tar(package_tmp_directory.name + '/control.tar.xz', manifest)
    write_data_tar(package_tmp_directory.name + '/data.tar.xz', installation_prefix, package_files)
    create_debfile(output_debfile_name, package_tmp_directory.name)

if __name__ == "__main__":
    main(sys.argv[1:])

Currently this script creates control.tar.xz with but I want to add a folder when creating control.tar.xz and then add control file to that folder.

Means currently the hierarchy is

control.tar.xz ==> control file

I want this hierarchy

control.tar.xz ==> folder name ==> control file

Basically the tar file contains not just the control file but a directory and the control file inside of that directory.

Golu
  • 350
  • 2
  • 14
  • Describe more. Try specifying the exact problem you are facing in the code. And try not to specify a problem statement and state what you want. – Reck Feb 24 '18 at 16:44
  • Thnx for your suggestions :) I reformatted my question now – Golu Feb 24 '18 at 17:15
  • Ok, you significantly decreased amount of code but there is still problem. We can't run it. Include all the imports and some minimal example of `contents`. – Georgy Feb 24 '18 at 17:41

1 Answers1

1

I modified a stackoverflow answer into tardata.py which looks for control.txt and places it in a data directory and calls the extracted file control.tar.gz.

import os
import tarfile

tar_file = "control.tar.gz"
source_file = "control.txt"
inside_dir = "data"

def make_tarfile(tar_file, source_file, inside_dir):
    with tarfile.open(tar_file, "w:gz") as tar:
        tar.add(source_file, arcname=os.path.join(inside_dir, source_file))

make_tarfile(tar_file, source_file, inside_dir)

The code uses the optional argument arcname which allows the creation of the directory inside the tar file. The tarfile.Add() documentation says

If given, arcname specifies an alternative name for the file in the archive.

Run

$ python tardata.py

Check the created tar files heirarchy

$ tar --list -f control.tar.gz
data/control.txt
SomeGuyOnAComputer
  • 5,414
  • 6
  • 40
  • 72
  • Is it possible by minor changes in my given code ? Actually I don't know Python (I m thinking to learn) and above code snippet is pasted from somewhere and I m just want to add a functionality by small changes but not by removing whole code .. – Golu Feb 25 '18 at 03:29
  • Learning Python would certainly help in fixing your python code... You can solve this two ways: 1) Copy and paste the function `def make_tarfile()` into your code before `write_control_tar` and call the function `make_tarfile("control.tar.gz", "control_file_name", "control")` then it should create a `control.tar.gz` with `control/control_file_name` inside it OR 2) add `arc_name` optional arg to your `.add()` function with the correct directory name. – SomeGuyOnAComputer Feb 25 '18 at 18:04
  • @Golu did my answer help? – SomeGuyOnAComputer Feb 26 '18 at 01:32
  • Thnx for your kind help :) and your second option is good bcz of small code changes but after adding arc_name in add() i am facing an error TypeError: add() got an unexpected keyword argument 'tarinfo' – Golu Feb 26 '18 at 02:58
  • Off course now I am posting full code for more clarifications – Golu Feb 26 '18 at 14:30
  • ...err you only want to provide a portion of your code. Don't post your entire source. It makes it harder to look through.. – SomeGuyOnAComputer Feb 26 '18 at 15:01
  • But it will make easier to solve my query now you can understand the working flow of above code ...plz i begging you i m noob i know u can easily do plz ... :( – Golu Feb 26 '18 at 16:25