blob: 29718c760d2e35e2fc3c262184951dd68d6bc7be [file] [log] [blame]
Benjamin Gordon1f4537f2019-12-06 09:10:56 -07001# Copyright 2020 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Run xz from PATH with a thread for each core in the system."""
6
Ben Pastenec6bf5492020-08-28 17:35:01 -07007from __future__ import division
Benjamin Gordon1f4537f2019-12-06 09:10:56 -07008
George Burgess IV757887f2021-09-22 15:58:35 -07009import getopt
Benjamin Gordon1f4537f2019-12-06 09:10:56 -070010import os
George Burgess IV757887f2021-09-22 15:58:35 -070011import subprocess
12import sys
Mike Frysinger687ab9d2020-02-06 00:35:15 -050013
Ben Pastenec6bf5492020-08-28 17:35:01 -070014from chromite.lib import commandline
15from chromite.lib import osutils
16from chromite.utils import memoize
17
Benjamin Gordon1f4537f2019-12-06 09:10:56 -070018
George Burgess IV757887f2021-09-22 15:58:35 -070019PIXZ_DISABLE_VAR = 'FOR_TEST_XZ_AUTO_NO_PIXZ'
20
21
Ben Pastenec6bf5492020-08-28 17:35:01 -070022@memoize.Memoize
23def HasPixz():
24 """Returns path to pixz if it's on PATH or None otherwise."""
George Burgess IV757887f2021-09-22 15:58:35 -070025 return PIXZ_DISABLE_VAR not in os.environ and osutils.Which('pixz')
Ben Pastenec6bf5492020-08-28 17:35:01 -070026
27
George Burgess IV757887f2021-09-22 15:58:35 -070028def ParsePixzArgs(argv):
29 """Determines flags to pass to pixz, per argv.
Denis Nikitin091a7c42021-10-27 18:55:27 +000030
George Burgess IV757887f2021-09-22 15:58:35 -070031 Returns:
32 A tuple containing:
33 - A raw list of flags to pass to pixz.
34 - An optional input file.
35 - An optional output file (only exists if the input file is present).
Denis Nikitin091a7c42021-10-27 18:55:27 +000036 """
George Burgess IV757887f2021-09-22 15:58:35 -070037 # Glancing at docs, the following opts are supported. -i and -o are ignored,
38 # since we assert in `main` that they're not present, but include parsing for
39 # them anyway.
40 flags, args = getopt.gnu_getopt(
41 args=argv,
42 shortopts='dlxi:o:0123456789p:tkch',
43 )
44 if not args:
45 file_to_compress = None
46 target = None
47 elif len(args) == 1:
48 file_to_compress = args[0]
49 target = None
50 else:
51 file_to_compress = args[0]
52 target = args[1]
53
54 raw_flag_list = []
55 for key, val in flags:
56 raw_flag_list.append(key)
57 if val:
58 raw_flag_list.append(val)
59
60 return raw_flag_list, file_to_compress, target
Ben Pastenec6bf5492020-08-28 17:35:01 -070061
62
George Burgess IV757887f2021-09-22 15:58:35 -070063def Execvp(argv):
64 """Execs the given argv."""
65 os.execvp(argv[0], argv)
66
67
68def ExecCompressCommand(stdout, argv):
69 """Execs compression command."""
70 # It appears that in order for pixz to do parallel decompression, compression
71 # needs to be done with pixz. xz itself is only capable of parallel
72 # compression.
73 if not HasPixz():
74 cmd = ['xz']
75
76 if stdout:
77 cmd.append('-zc')
78 else:
79 cmd.append('-z')
80 cmd += argv
81 Execvp(cmd)
82
83 cmd = ['pixz']
84 raw_flag_list, compressed_file_name, output_file = ParsePixzArgs(argv)
85
86 autodelete_input_file = False
87 if not compressed_file_name:
88 assert not output_file
89 compressed_file_name = '/dev/stdin'
90 output_file = '/dev/stdout'
91 elif stdout:
92 output_file = '/dev/stdout'
93 elif not output_file:
94 # Pixz defaults to a `.pxz` suffix (or `.tpxz` if it's compressing a
95 # tar file). We need the suffix to be consistent, so force it here.
96 output_file = f'{compressed_file_name}.xz'
97 autodelete_input_file = True
98
99 cmd += raw_flag_list
100 cmd.append(compressed_file_name)
101 if output_file:
102 cmd.append(output_file)
103
104 if not autodelete_input_file:
105 Execvp(cmd)
106
107 return_code = subprocess.call(cmd)
108 if not return_code:
109 os.unlink(compressed_file_name)
110 sys.exit(return_code)
111
112
113def ExecDecompressCommand(stdout, argv):
114 """Execs decompression command."""
Ben Pastenec6bf5492020-08-28 17:35:01 -0700115 if HasPixz():
George Burgess IV757887f2021-09-22 15:58:35 -0700116 cmd = ['pixz']
117 cmd.append('-d')
118
119 _, compressed_file_name, _ = ParsePixzArgs(argv)
Tiancong Wangac3fc4a2020-09-11 10:44:03 -0700120 if stdout:
121 # Explicitly tell pixz the file is the input, so it will dump the output
122 # to stdout, instead of automatically choosing an output name.
123 cmd.append('-i')
George Burgess IV757887f2021-09-22 15:58:35 -0700124 if not compressed_file_name:
125 cmd.append('/dev/stdin')
126 elif not compressed_file_name:
127 cmd += ['-i', '/dev/stdin']
128 cmd += argv
129 Execvp(cmd)
130
131 cmd = ['xz']
Tiancong Wangac3fc4a2020-09-11 10:44:03 -0700132 if stdout:
George Burgess IV757887f2021-09-22 15:58:35 -0700133 cmd.append('-dc')
134 else:
135 cmd.append('-d')
136 cmd += argv
137 Execvp(cmd)
Ben Pastenec6bf5492020-08-28 17:35:01 -0700138
139
140def GetParser():
141 """Return a command line parser."""
142 parser = commandline.ArgumentParser(description=__doc__)
143 parser.add_argument(
Tiancong Wangac3fc4a2020-09-11 10:44:03 -0700144 '-d',
145 '--decompress',
146 '--uncompress',
Ben Pastenec6bf5492020-08-28 17:35:01 -0700147 help='Decompress rather than compress.',
148 action='store_true')
Tiancong Wangac3fc4a2020-09-11 10:44:03 -0700149 parser.add_argument(
150 '-c',
151 dest='stdout',
152 action='store_true',
153 help="Write to standard output and don't delete input files.")
Ben Pastenec6bf5492020-08-28 17:35:01 -0700154 return parser
155
156
Benjamin Gordon1f4537f2019-12-06 09:10:56 -0700157def main(argv):
Ben Pastenec6bf5492020-08-28 17:35:01 -0700158 parser = GetParser()
159 known_args, argv = parser.parse_known_args()
Tiancong Wangac3fc4a2020-09-11 10:44:03 -0700160 if '-i' in argv or '-o' in argv:
161 parser.error('It is invalid to use -i or -o with xz_auto')
162
Ben Pastenec6bf5492020-08-28 17:35:01 -0700163 if known_args.decompress:
George Burgess IV757887f2021-09-22 15:58:35 -0700164 ExecDecompressCommand(known_args.stdout, argv)
Tiancong Wangac3fc4a2020-09-11 10:44:03 -0700165 else:
George Burgess IV757887f2021-09-22 15:58:35 -0700166 ExecCompressCommand(known_args.stdout, argv)