summaryrefslogtreecommitdiffstats
path: root/build/scripts/tar_directory.py
blob: bb5b0fe7d29df1b0322b0f367570e1f6b9ba80f5 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import os
import argparse
import tarfile
import subprocess
import stat


def is_exe(fpath: str) -> bool:
    return os.path.isfile(fpath) and os.access(fpath, os.X_OK)


def _usage() -> str:
    return "\n".join(
        [
            "Usage:",
            f"`tar_directory.py [--tar-by-py] [--exclude fileOrDir0 ... --exclude fileOrDirN] archive.tar directory [skip prefix]`",
            "or",
            f"`tar_directory.py [--tar-by-py] --extract archive.tar output_directory`",
        ]
    )


def main():
    parser = argparse.ArgumentParser(description='Make or extract tar archive')
    parser.add_argument('--extract', action="store_true", default=False, help="Extract archive")
    parser.add_argument('--tar-by-py', action="store_true", default=False, help="Force use taring by python")
    parser.add_argument(
        '--exclude', type=str, action='append', default=[], help="Exclude for create archive (can use multiply times)"
    )
    parser.add_argument('archive', type=str, action='store', help="Archive name, for example, archive.tar")  # required
    parser.add_argument(
        'directory', type=str, action='store', help="Directory name for create from or extract to"
    )  # required
    parser.add_argument(
        'prefix', type=str, nargs='?', action='store', help="Path prefix for skip before create archive"
    )  # dont't required, because nargs=?

    args = parser.parse_args()

    if args.extract and (args.exclude or args.prefix):
        raise Exception(f"Illegal usage: {" ".join(args)}\n{_usage()}")

    tar = args.archive
    directory = args.directory
    prefix = args.prefix

    if args.extract:
        dest = os.path.abspath(directory)
        if not os.path.exists(dest):
            os.makedirs(dest)

    for tar_exe in ('/usr/bin/tar', '/bin/tar') if not args.tar_by_py else []:
        if not is_exe(tar_exe):
            continue
        if args.extract:
            command = [tar_exe, '-xf', tar, '-C', dest]
        else:
            source = os.path.relpath(directory, prefix) if prefix else directory
            command = (
                [tar_exe]
                + (['--exclude=' + exclude for exclude in args.exclude] if args.exclude else [])
                + ['-cf', tar]
                + (['-C', prefix] if prefix else [])
                + [source]
            )
        subprocess.run(command, check=True)
        break
    else:
        if args.extract:
            with tarfile.open(tar, 'r') as tar_file:
                tar_file.extractall(dest)
        else:
            source = directory
            with tarfile.open(tar, 'w') as out:

                def filter(tarinfo: tarfile.TarInfo):
                    if args.exclude:
                        for exclude in args.exclude:
                            if tarinfo.name.endswith(exclude):
                                return None
                    tarinfo.mode = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH if tarinfo.mode | stat.S_IXUSR else 0
                    tarinfo.mode = (
                        tarinfo.mode | stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IWGRP | stat.S_IROTH
                    )
                    # Set mtime to 1970-01-01 00:00:01, else if mtime == 0
                    # it not used here https://a.yandex-team.ru/arcadia/contrib/python/python-libarchive/py3/libarchive/__init__.py#L355
                    # But mtime != 0 also ignored by libarchive too :((
                    tarinfo.mtime = 1
                    tarinfo.uid = 0
                    tarinfo.gid = 0
                    tarinfo.uname = 'dummy'
                    tarinfo.gname = 'dummy'
                    return tarinfo

                out.add(
                    os.path.abspath(source),
                    arcname=os.path.relpath(source, prefix) if prefix else source,
                    filter=filter,
                )


if __name__ == '__main__':
    main()