git-cinnabar
184 строки · 5.4 Кб
1# This Source Code Form is subject to the terms of the Mozilla Public
2# License, v. 2.0. If a copy of the MPL was not distributed with this
3# file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5import concurrent.futures
6import os
7import shutil
8import struct
9import subprocess
10import sys
11import tarfile
12import zipfile
13from io import BytesIO
14from pathlib import Path
15from urllib.request import urlopen
16
17
18def main():
19ret = 0
20args = sys.argv[1:]
21if args and args[0] == "--download":
22download_py = Path(__file__).parent.parent / "download.py"
23args = (
24subprocess.check_output(
25[sys.executable, download_py, "--url", "--list"] + args[1:]
26)
27.decode()
28.splitlines()
29)
30else:
31for arg in args:
32if arg.startswith("-"):
33print(f"{arg} is not supported.")
34return 1
35if len(args) > 1:
36executor = concurrent.futures.ProcessPoolExecutor()
37map_ = executor.map
38else:
39map_ = map
40for path, pkg in zip(args, map_(package_from, args)):
41if pkg:
42print(f"Created {pkg} from {path}", file=sys.stderr)
43else:
44print(f"Can't determine platform type for {path}", file=sys.stderr)
45ret = 1
46return ret
47
48
49def package_from(path):
50if path.startswith(("http:", "https:")):
51fh = urlopen(path)
52size = fh.length
53else:
54fh = open(path, "rb")
55size = os.stat(path).st_size
56with fh:
57fh = RewindOnce(fh)
58system, machine = detect_platform(fh)
59if not system or not machine:
60return
61
62fh.rewind()
63if size is None:
64fh = BytesIO(fh.read())
65size = len(fh.getbuffer())
66return package(fh, size, system, machine)
67
68
69def package(fh, size, system, machine):
70stem = f"git-cinnabar.{system.lower()}.{machine.lower()}"
71if system == "Windows":
72pkg = f"{stem}.zip"
73zip = zipfile.ZipFile(
74pkg, mode="w", compression=zipfile.ZIP_DEFLATED, compresslevel=9
75)
76# Manually do zip.mkdir("git-cinnabar") until we can depend on python 3.11
77zinfo = zipfile.ZipInfo("git-cinnabar/")
78zinfo.external_attr = (0o40777 << 16) | 0x10
79zip.writestr(zinfo, "")
80fh = RewindOnce(fh)
81with zip.open("git-cinnabar/git-cinnabar.exe", mode="w") as zipped:
82shutil.copyfileobj(fh, zipped)
83fh.rewind()
84with zip.open("git-cinnabar/git-remote-hg.exe", mode="w") as zipped:
85shutil.copyfileobj(fh, zipped)
86else:
87pkg = f"{stem}.tar.xz"
88tar = tarfile.open(pkg, mode="w:xz", preset=9)
89info = tarinfo("git-cinnabar/")
90info.mode = 0o755
91info.type = tarfile.DIRTYPE
92tar.addfile(info)
93
94info = tarinfo("git-cinnabar/git-cinnabar")
95info.mode = 0o700
96info.size = size
97info.type = tarfile.REGTYPE
98tar.addfile(info, fh)
99
100info = tarinfo("git-cinnabar/git-remote-hg")
101info.mode = 0o777
102info.type = tarfile.SYMTYPE
103info.linkname = "git-cinnabar"
104tar.addfile(info)
105return pkg
106
107
108def tarinfo(name):
109info = tarfile.TarInfo(name)
110info.uid = 1000
111info.gid = 1000
112info.uname = "cinnabar"
113info.gname = "cinnabar"
114return info
115
116
117class RewindOnce:
118def __init__(self, fh):
119self.buf = b""
120self.off = 0
121self.rewound = False
122self.fh = fh
123
124def read(self, length=None):
125if self.rewound:
126if length is None:
127return self.buf[self.off :] + self.fh.read()
128ret = self.buf[self.off :][:length]
129self.off += len(ret)
130missing = length - len(ret)
131if not missing:
132return ret
133return ret + self.fh.read(missing)
134
135ret = self.fh.read(length)
136self.buf += ret
137return ret
138
139def rewind(self):
140assert not self.rewound
141self.rewound = True
142
143
144def detect_platform(executable):
145system, machine = None, None
146head = executable.read(4)
147if head[:2] == b"MZ":
148# Seek to 0x3c
149executable.read(0x3C - 4)
150(pe_offset,) = struct.unpack("<L", executable.read(4))
151# Seek to pe_offset
152executable.read(pe_offset - 0x40)
153pe_signature = executable.read(4)
154if pe_signature == b"PE\0\0":
155system = "Windows"
156(machine_type,) = struct.unpack("<H", executable.read(2))
157if machine_type == 0x8664:
158machine = "x86_64"
159elif head == b"\xcf\xfa\xed\xfe":
160system = "macOS"
161(machine_type,) = struct.unpack("<L", executable.read(4))
162if machine_type == 0x1000007:
163machine = "x86_64"
164elif machine_type == 0x100000C:
165machine = "arm64"
166elif head == b"\x7fELF":
167(ident,) = struct.unpack(">L", executable.read(4))
168# 64-bits little-endian Linux (in theory, System-V)
169if ident == 0x02010100:
170system = "Linux"
171# Seek to 0x12
172executable.read(10)
173(machine_type,) = struct.unpack("<H", executable.read(2))
174if machine_type == 0x3E:
175machine = "x86_64"
176elif machine_type == 0xB7:
177machine = "arm64"
178if system and machine:
179return system, machine
180return None, None
181
182
183if __name__ == "__main__":
184sys.exit(main())
185