1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3#
4# Hardlink only packages used in the build from one directory to another,
5# and updates the RemotePackageRepository file at the same time.
6#
7# Copyright 2017-2020 Augustin Cavalier <waddlesplash>
8# Distributed under the terms of the MIT License.
9
10import sys, os, subprocess, re, hashlib
11from pkg_resources import parse_version
12
13# Detect architecture from provided jam remote package repo file
14def probe_architecture(jamf):
15	text = ""
16	with open(jamf) as f:
17		for readline in f:
18			line_strip = readline.strip()
19			text += line_strip
20	return text.split(":")[1].strip()
21
22if len(sys.argv) != 4:
23	print("usage: hardlink_packages.py [jam RemotePackageRepository file] "
24		+ "[prebuilt packages directory] [destination root directory]")
25	print("  note that the [jam RemotePackageRepository file] will be modified.")
26	print("  note that [target directory] is assumed to have a 'packages' subdirectory, "
27		+ "and a repo.info.template file (using $ARCH$)")
28	sys.exit(1)
29
30if subprocess.run(['package_repo'], None, None, None,
31		subprocess.DEVNULL, subprocess.PIPE).returncode != 1:
32	print("package_repo command does not seem to exist.")
33	sys.exit(1)
34
35args_jamf = sys.argv[1]
36args_src = sys.argv[2]
37args_dst = sys.argv[3]
38arch = probe_architecture(args_jamf)
39
40print("Detected Architecture: " + arch)
41
42if not args_dst.endswith('/'):
43	args_dst = args_dst + '/'
44if not args_src.endswith('/'):
45	args_src = args_src + '/'
46
47args_dst_packages = args_dst + 'packages/'
48
49packageVersions = []
50for filename in os.listdir(args_src):
51	if (not (filename.endswith("-" + arch + ".hpkg")) and
52			not (filename.endswith("-any.hpkg"))):
53		continue
54	packageVersions.append(filename)
55
56# Read RemotePackageRepository file and hardlink relevant packages
57pattern = re.compile("^[a-z0-9]")
58newFileForJam = []
59packageFiles = []
60filesNotFound = False
61with open(args_jamf) as f:
62	for line in f:
63		pkg = line.strip()
64		if (len(pkg) == 0):
65			continue
66		if not (pattern.match(pkg)):
67			# not a package (probably a Jam directive)
68			newFileForJam.append(line)
69			continue
70
71		try:
72			pkgname = pkg[:pkg.index('-')]
73		except:
74			pkgname = ''
75		if (len(pkgname) == 0):
76			# no version, likely a source/debuginfo listing
77			newFileForJam.append(line)
78			continue
79
80		greatestVersion = None
81		for pkgVersion in packageVersions:
82			if (pkgVersion.startswith(pkgname + '-')):
83				if ((greatestVersion == None) or parse_version(pkgVersion) > parse_version(greatestVersion)):
84					greatestVersion = pkgVersion
85		if (greatestVersion == None):
86			print("not found: " + pkg)
87			newFileForJam.append(line)
88			filesNotFound = True
89			continue
90		else:
91			# found it, so hardlink it
92			if not (os.path.exists(args_dst_packages + greatestVersion)):
93				os.link(args_src + greatestVersion, args_dst_packages + greatestVersion)
94			if ('packages/' + greatestVersion) not in packageFiles:
95				packageFiles.append('packages/' + greatestVersion)
96			# also hardlink the source package, if one exists
97			srcpkg = greatestVersion.replace("-" + arch + ".hpkg",
98				"-source.hpkg").replace('-', '_source-', 1)
99			if os.path.exists(args_src + srcpkg):
100				if not os.path.exists(args_dst_packages + srcpkg):
101					os.link(args_src + srcpkg, args_dst_packages + srcpkg)
102				if ('packages/' + srcpkg) not in packageFiles:
103					packageFiles.append('packages/' + srcpkg)
104		newFileForJam.append("\t" + greatestVersion[:greatestVersion.rfind('-')] + "\n");
105
106if filesNotFound:
107	sys.exit(1)
108
109finalizedNewFile = "".join(newFileForJam).encode('UTF-8')
110with open(args_jamf, 'wb') as f:
111	f.write(finalizedNewFile)
112
113listhash = hashlib.sha256(finalizedNewFile).hexdigest()
114try:
115	os.mkdir(args_dst + listhash)
116except:
117	print("dir " + listhash + " already exists. No changes?")
118	sys.exit(1)
119
120repodir = args_dst + listhash + '/'
121os.symlink('../packages', repodir + 'packages')
122
123with open(args_dst + 'repo.info.template', 'r') as ritf:
124	repoInfoTemplate = ritf.read()
125
126repoInfoTemplate = repoInfoTemplate.replace("$ARCH$", arch)
127with open(repodir + 'repo.info', 'w') as rinf:
128	rinf.write(repoInfoTemplate)
129
130packageFiles.sort()
131with open(repodir + 'package.list', 'w') as pkgl:
132	pkgl.write("\n".join(packageFiles))
133
134if os.system('cd ' + repodir + ' && package_repo create repo.info ' + " ".join(packageFiles)) != 0:
135	print("failed to create package repo.")
136	sys.exit(1)
137
138if os.system('cd ' + repodir + ' && sha256sum repo >repo.sha256') != 0:
139	print("failed to checksum package repo.")
140	sys.exit(1)
141