mirror of
https://github.com/toyota-connected/emb_cli.git
synced 2026-06-21 07:19:37 -07:00
Merge pull request #54 from toyota-connected/fix/apt-deb822-sources
fix(cross): read deb822 apt sources (trixie/raspios) for -dev resolution
This commit is contained in:
@@ -114,31 +114,82 @@ AptIndex parsePackagesIndex(String text, {required String repoBase}) {
|
||||
}
|
||||
|
||||
/// Compressed `Packages`-index URLs for [arch], parsed from a sysroot's apt
|
||||
/// sources (the one-line `deb [opts] <uri> <suite> <comp>...` format). One URL
|
||||
/// per (source, component). `deb-src`, comments, and blanks are skipped.
|
||||
/// sources. Handles both the classic one-line `deb` format and the deb822
|
||||
/// `Types:/URIs:/Suites:/Components:` stanzas used by trixie/raspios. One URL
|
||||
/// per (source, component); `deb-src`, disabled entries, comments, and blanks
|
||||
/// are skipped.
|
||||
List<String> aptIndexUrls(String sourcesText, String arch) {
|
||||
final urls = <String>[];
|
||||
for (final line in sourcesText.split('\n')) {
|
||||
final t = line.trim();
|
||||
if (!t.startsWith('deb ')) continue;
|
||||
var toks = t.substring(4).trim().split(RegExp(r'\s+'));
|
||||
if (toks.isNotEmpty && toks.first.startsWith('[')) {
|
||||
var i = 0;
|
||||
while (i < toks.length && !toks[i].endsWith(']')) {
|
||||
i++;
|
||||
// Process blank-line-separated stanzas so deb822 entries (multi-line) don't
|
||||
// bleed into one-line parsing. _readAptSources separates files with a blank
|
||||
// line, so a one-line sources.list and a deb822 *.sources never mix.
|
||||
for (final stanza in sourcesText.split(RegExp(r'\n[ \t]*\n'))) {
|
||||
if (RegExp(r'^[ \t]*Types[ \t]*:', multiLine: true).hasMatch(stanza)) {
|
||||
urls.addAll(_deb822IndexUrls(stanza, arch));
|
||||
} else {
|
||||
for (final line in stanza.split('\n')) {
|
||||
urls.addAll(_oneLineIndexUrls(line, arch));
|
||||
}
|
||||
toks = i + 1 < toks.length ? toks.sublist(i + 1) : const [];
|
||||
}
|
||||
if (toks.length < 3) continue;
|
||||
final uri = toks[0];
|
||||
final suite = toks[1];
|
||||
for (final comp in toks.sublist(2)) {
|
||||
urls.add('$uri/dists/$suite/$comp/binary-$arch/Packages.xz');
|
||||
}
|
||||
}
|
||||
return urls;
|
||||
}
|
||||
|
||||
/// One classic `deb [opts] <uri> <suite> <comp...>` line -> binary index URLs.
|
||||
List<String> _oneLineIndexUrls(String line, String arch) {
|
||||
final t = line.trim();
|
||||
if (!t.startsWith('deb ')) return const [];
|
||||
var toks = t.substring(4).trim().split(RegExp(r'\s+'));
|
||||
if (toks.isNotEmpty && toks.first.startsWith('[')) {
|
||||
var i = 0;
|
||||
while (i < toks.length && !toks[i].endsWith(']')) {
|
||||
i++;
|
||||
}
|
||||
toks = i + 1 < toks.length ? toks.sublist(i + 1) : const [];
|
||||
}
|
||||
if (toks.length < 3) return const [];
|
||||
final uri = toks[0];
|
||||
final suite = toks[1];
|
||||
return [
|
||||
for (final comp in toks.sublist(2))
|
||||
'$uri/dists/$suite/$comp/binary-$arch/Packages.xz',
|
||||
];
|
||||
}
|
||||
|
||||
/// A deb822 stanza (`Types:`/`URIs:`/`Suites:`/`Components:`, as used by
|
||||
/// trixie/raspios) -> binary index URLs for each URI x Suite x Component.
|
||||
List<String> _deb822IndexUrls(String stanza, String arch) {
|
||||
final fields = <String, String>{};
|
||||
String? key;
|
||||
for (final line in stanza.split('\n')) {
|
||||
final m = RegExp(
|
||||
r'^([A-Za-z][A-Za-z-]*)[ \t]*:[ \t]*(.*)$',
|
||||
).firstMatch(line);
|
||||
if (m != null) {
|
||||
key = m.group(1);
|
||||
fields[key!] = (m.group(2) ?? '').trim();
|
||||
} else if (key != null && (line.startsWith(' ') || line.startsWith('\t'))) {
|
||||
fields[key] = '${fields[key]} ${line.trim()}'.trim();
|
||||
}
|
||||
}
|
||||
List<String> values(String k) => (fields[k] ?? '')
|
||||
.split(RegExp(r'\s+'))
|
||||
.where((s) => s.isNotEmpty)
|
||||
.toList();
|
||||
if (!values('Types').contains('deb')) return const [];
|
||||
if ((fields['Enabled'] ?? 'yes').toLowerCase() == 'no') return const [];
|
||||
final out = <String>[];
|
||||
for (final uri in values('URIs')) {
|
||||
final base = uri.replaceFirst(RegExp(r'/+$'), '');
|
||||
for (final suite in values('Suites')) {
|
||||
for (final comp in values('Components')) {
|
||||
out.add('$base/dists/$suite/$comp/binary-$arch/Packages.xz');
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/// Names already installed in a sysroot's `/var/lib/dpkg/status` (plus what
|
||||
/// they Provide) — treated as already satisfied so they aren't re-downloaded.
|
||||
Set<String> parseInstalled(String statusText) {
|
||||
|
||||
@@ -377,18 +377,29 @@ class ArmGnuCrossProvider implements CrossProvider {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Concatenate the sysroot's `/etc/apt/sources.list` and
|
||||
/// `sources.list.d/*.list` for [aptIndexUrls].
|
||||
/// Concatenate the sysroot's `/etc/apt/sources.list`, the one-line
|
||||
/// `sources.list.d/*.list`, and the deb822 `sources.list.d/*.sources`
|
||||
/// (trixie/raspios) for [aptIndexUrls]. Files are separated by a blank line
|
||||
/// so one-line and deb822 stanzas never merge.
|
||||
String _readAptSources(Directory sysrootDir) {
|
||||
final buf = StringBuffer();
|
||||
final main = File(p.join(sysrootDir.path, 'etc', 'apt', 'sources.list'));
|
||||
if (main.existsSync()) buf.writeln(main.readAsStringSync());
|
||||
void add(File f) {
|
||||
if (f.existsSync()) {
|
||||
buf
|
||||
..writeln(f.readAsStringSync())
|
||||
..writeln();
|
||||
}
|
||||
}
|
||||
|
||||
add(File(p.join(sysrootDir.path, 'etc', 'apt', 'sources.list')));
|
||||
final dir = Directory(
|
||||
p.join(sysrootDir.path, 'etc', 'apt', 'sources.list.d'),
|
||||
);
|
||||
if (dir.existsSync()) {
|
||||
for (final f in dir.listSync().whereType<File>()) {
|
||||
if (f.path.endsWith('.list')) buf.writeln(f.readAsStringSync());
|
||||
final files = dir.listSync().whereType<File>().toList()
|
||||
..sort((a, b) => a.path.compareTo(b.path));
|
||||
for (final f in files) {
|
||||
if (f.path.endsWith('.list') || f.path.endsWith('.sources')) add(f);
|
||||
}
|
||||
}
|
||||
return buf.toString();
|
||||
|
||||
@@ -114,5 +114,51 @@ deb-src http://deb.debian.org/debian bookworm main
|
||||
'http://archive.raspberrypi.com/debian/dists/bookworm/main/binary-arm64/Packages.xz',
|
||||
]);
|
||||
});
|
||||
|
||||
test('parses deb822 stanzas (trixie/raspios .sources)', () {
|
||||
// URIs has a trailing slash (raspi.sources); Suites + Components are
|
||||
// space-separated multi-value; deb-src and a disabled stanza are skipped.
|
||||
const deb822 = '''
|
||||
Types: deb deb-src
|
||||
URIs: http://deb.debian.org/debian
|
||||
Suites: trixie trixie-updates
|
||||
Components: main contrib
|
||||
|
||||
Types: deb
|
||||
URIs: http://archive.raspberrypi.com/debian/
|
||||
Suites: trixie
|
||||
Components: main
|
||||
|
||||
Types: deb
|
||||
URIs: http://disabled.example/debian
|
||||
Suites: trixie
|
||||
Components: main
|
||||
Enabled: no
|
||||
''';
|
||||
expect(aptIndexUrls(deb822, 'arm64'), [
|
||||
'http://deb.debian.org/debian/dists/trixie/main/binary-arm64/Packages.xz',
|
||||
'http://deb.debian.org/debian/dists/trixie/contrib/binary-arm64/Packages.xz',
|
||||
'http://deb.debian.org/debian/dists/trixie-updates/main/binary-arm64/Packages.xz',
|
||||
'http://deb.debian.org/debian/dists/trixie-updates/contrib/binary-arm64/Packages.xz',
|
||||
// trailing slash on URIs is normalized (no //dists)
|
||||
'http://archive.raspberrypi.com/debian/dists/trixie/main/binary-arm64/Packages.xz',
|
||||
]);
|
||||
});
|
||||
|
||||
test('one-line and deb822 files mix without bleeding', () {
|
||||
// Mimics _readAptSources: files separated by a blank line.
|
||||
const mixed = '''
|
||||
deb http://deb.debian.org/debian bookworm main
|
||||
|
||||
Types: deb
|
||||
URIs: http://archive.raspberrypi.com/debian
|
||||
Suites: trixie
|
||||
Components: main
|
||||
''';
|
||||
expect(aptIndexUrls(mixed, 'arm64'), [
|
||||
'http://deb.debian.org/debian/dists/bookworm/main/binary-arm64/Packages.xz',
|
||||
'http://archive.raspberrypi.com/debian/dists/trixie/main/binary-arm64/Packages.xz',
|
||||
]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user