Skip to content

Commit e613ca8

Browse files
authored
Merge pull request #19 from CubeCoders/wine
feat(wine): refine dep resolution
2 parents 81cbb14 + 3328a7f commit e613ca8

9 files changed

Lines changed: 523 additions & 428 deletions

File tree

Lines changed: 33 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,47 @@
11
#!/bin/sh
22

3-
set -eux
3+
set -eu
4+
5+
if [ "$#" -lt 1 ]; then
6+
echo "Usage: $0 <file1> [file2 ...]" >&2
7+
exit 2
8+
fi
49

510
TMPDIR="$(mktemp -d)"
6-
trap 'rm -rf "${TMPDIR}"' EXIT INT TERM
11+
trap 'rm -rf "$TMPDIR"' EXIT INT TERM
712

8-
# Normalize: strip CR, trim, drop blanks, unique
913
norm() {
10-
# shellcheck disable=SC2002
11-
cat "$1" \
12-
| sed 's/\r$//' \
13-
| sed -e 's/^[[:space:]]\+//' -e 's/[[:space:]]\+$//' \
14-
| awk 'NF' \
15-
| sort -u
14+
# $1 -> normalized unique list on stdout
15+
sed 's/\r$//' "$1" \
16+
| sed -e 's/^[[:space:]]\+//' -e 's/[[:space:]]\+$//' \
17+
| awk 'NF' \
18+
| sort -u
1619
}
1720

21+
# Normalize each input
1822
i=0
1923
for f in "$@"; do
20-
i=$((i+1))
21-
norm "${f}" >"${TMPDIR}/f${i}.norm"
24+
i=$((i+1))
25+
norm "$f" >"${TMPDIR}/f${i}.norm"
2226
done
2327

24-
# Intersect all normalized files (empty if any input empty)
25-
intersect() {
26-
set -- "$@"
27-
nonempty=
28-
for g in "$@"; do
29-
[ -s "${g}" ] && nonempty="${nonempty} ${g}"
30-
done
31-
[ -n "${nonempty}" ] || { :; return 0; }
32-
33-
# shellcheck disable=SC2086
34-
set -- ${nonempty}
35-
cp "$1" "${TMPDIR}/common"
36-
shift
37-
while [ "$#" -gt 0 ]; do
38-
grep -Fxf "$1" "${TMPDIR}/common" >"${TMPDIR}/next" || true
39-
mv "${TMPDIR}/next" "${TMPDIR}/common"
40-
shift
41-
done
42-
cat "${TMPDIR}/common"
43-
}
44-
45-
# Build the list of normalized files
46-
FILES=$(seq 1 "$i" | sed "s#^#${TMPDIR}/f#g; s/\$/\.norm/")
28+
# Intersect all non-empty normalized files
29+
# If any file is empty, intersection is empty.
30+
nonempty=""
31+
for g in "${TMPDIR}"/f*.norm; do
32+
[ -s "$g" ] || { : > "${TMPDIR}/common"; echo -n ""; cat "${TMPDIR}/common"; exit 0; }
33+
nonempty="${nonempty} ${g}"
34+
done
4735

48-
# shellcheck disable=SC2086
49-
COMMON="$(intersect ${FILES} || true)"
36+
# Seed with the first, then narrow with grep -Fxf
37+
set -- ${nonempty}
38+
cp "$1" "${TMPDIR}/common"
39+
shift
40+
while [ "$#" -gt 0 ]; do
41+
# grep -Fxf returns 1 if no matches; we still want an empty file
42+
grep -Fxf "$1" "${TMPDIR}/common" > "${TMPDIR}/next" || true
43+
mv "${TMPDIR}/next" "${TMPDIR}/common"
44+
shift
45+
done
5046

51-
# Print in the order of the first file’s normalized list
52-
if [ -n "${COMMON}" ]; then
53-
printf '%s\n' "${COMMON}" >"${TMPDIR}/common.set"
54-
awk 'NR==FNR { keep[$0]=1; next } keep[$0]' "${TMPDIR}/common.set" "${TMPDIR}/f1.norm"
55-
fi
47+
cat "${TMPDIR}/common"

scripts/wine/find-deps.sh

Lines changed: 47 additions & 182 deletions
Original file line numberDiff line numberDiff line change
@@ -1,191 +1,56 @@
11
#!/bin/sh
22

3-
set -eux
3+
set -eu
44

55
BUILD="${1:-stable}"
66
WINE_DIST="${WINE_DIST:-bookworm}"
7-
WINE_FILES_DIR="${WINE_FILES_DIR:-/tmp/wine-files}"
8-
WINE_LINK="https://dl.winehq.org/wine-builds/debian/pool/main/w/wine$( [ "${BUILD}" = staging ] && printf -- '-staging' || true)/"
97

10-
case "${BUILD}" in
11-
stable|devel|staging)
12-
WINE_BRANCH="${BUILD}"
13-
MAJOR=""
14-
;;
15-
*-stable)
16-
WINE_BRANCH="stable"
17-
MAJOR="${BUILD%%-*}"
18-
;;
19-
*)
20-
echo "Unknown build: ${BUILD}" >&2
21-
exit 1
22-
;;
23-
esac
24-
25-
latest_version() {
26-
branch="$1"
27-
major="${2:-}"
28-
29-
# List matching debs, extract version, optional major filter, pick latest
30-
curl -fsSL "${WINE_LINK}" \
31-
| grep -oE "wine-${branch}-amd64_[0-9][0-9.]*~${WINE_DIST}(-[0-9]+)?_amd64\.deb" \
32-
| sed -E "s/^wine-${branch}-amd64_([0-9.]+)~${WINE_DIST}(-[0-9]+)?_amd64\.deb$/\1/" \
33-
| { [ -n "${major}" ] && awk -F. -v m="${major}" '$1==m' || cat; } \
34-
| sort -V \
35-
| tail -1
8+
# Helper: print Inst package names (sans wine metas), sorted unique
9+
_print_inst() {
10+
awk '
11+
$1=="Inst" {
12+
pkg=$2; sub(/\).*$/,"",pkg) # trim trailing ")..."
13+
# Drop wine meta/self packages
14+
if (pkg ~ /^(wine(|32|64)|winehq|wine-(devel|staging|stable))(:|$)/) next
15+
gsub(/[[:space:]]+/,"",pkg)
16+
print pkg
17+
}
18+
' | sort -u
3619
}
3720

38-
WINE_VERSION="$(latest_version "${WINE_BRANCH}" "${MAJOR}")"
39-
40-
# Helper: select the actual file name (keeps the real -<rev>)
41-
pick_file() {
42-
patt="$1"
43-
curl -fsSL "${WINE_LINK}" \
44-
| grep -oE "${patt}" \
45-
| sort -V \
46-
| tail -1
47-
}
48-
49-
DEB_A1="$(pick_file "wine-${WINE_BRANCH}-amd64_${WINE_VERSION}~${WINE_DIST}(-[0-9]+)?_amd64\.deb")"
50-
DEB_A2="$(pick_file "wine-${WINE_BRANCH}_${WINE_VERSION}~${WINE_DIST}(-[0-9]+)?_amd64\.deb")"
51-
DEB_A3="$(pick_file "winehq-${WINE_BRANCH}_${WINE_VERSION}~${WINE_DIST}(-[0-9]+)?_amd64\.deb")"
52-
DEB_B1="$(pick_file "wine-${WINE_BRANCH}-i386_${WINE_VERSION}~${WINE_DIST}(-[0-9]+)?_i386\.deb")"
53-
54-
mkdir -p "${WINE_FILES_DIR}"
55-
56-
wget -q -P "${WINE_FILES_DIR}" -- "${WINE_LINK}${DEB_A1}"
57-
wget -q -P "${WINE_FILES_DIR}" -- "${WINE_LINK}${DEB_A2}"
58-
wget -q -P "${WINE_FILES_DIR}" -- "${WINE_LINK}${DEB_A3}"
59-
wget -q -P "${WINE_FILES_DIR}" -- "${WINE_LINK}${DEB_B1}"
60-
61-
: >/tmp/wine-reqs.amd64
62-
: >/tmp/wine-reqs.i386
63-
64-
# Collect dependencies (64-bit)
65-
for deb in "${WINE_FILES_DIR}"/"${DEB_A1}" "${WINE_FILES_DIR}"/"${DEB_A2}" "${WINE_FILES_DIR}"/"${DEB_A3}"; do
66-
{
67-
dpkg-deb -I "${deb}" \
68-
| awk -F': ' '/^( Depends| Recommends):/{print $2}' \
69-
| tr ',' '\n' \
70-
| sed -E 's/\(.*\)//; s/^[[:space:]]+|[[:space:]]+$//g' \
71-
| grep -E '.' \
72-
| grep -Ev '^(wine|winehq)[[:alnum:]-]*$'
73-
} >> /tmp/wine-reqs.amd64 || :
74-
done
75-
76-
# Collect dependencies (32-bit)
77-
{
78-
dpkg-deb -I "${WINE_FILES_DIR}"/"${DEB_B1}" \
79-
| awk -F': ' '/^( Depends| Recommends):/{print $2}' \
80-
| tr ',' '\n' \
81-
| sed -E 's/\(.*\)//; s/^[[:space:]]+|[[:space:]]+$//g' \
82-
| grep -E '.' \
83-
| grep -Ev '^(wine|winehq)[[:alnum:]-]*$'
84-
} >> /tmp/wine-reqs.i386 || :
85-
86-
NATIVE="$(dpkg --print-architecture 2>/dev/null)"
87-
case "${NATIVE}" in
88-
amd64) COMPAT="i386";;
89-
arm64) COMPAT="armhf";;
21+
case "${BUILD}" in
22+
stable|devel|staging)
23+
apt-get -s install -y -o APT::Install-Recommends=1 "winehq-${BUILD}" \
24+
| _print_inst
25+
;;
26+
27+
*-stable)
28+
WINE_BRANCH="stable"
29+
MAJOR="${BUILD%%-*}"
30+
31+
WINE_LINK="${WINE_LINK:-https://dl.winehq.org/wine-builds/debian/pool/main/w/wine/}"
32+
WINE_BUILD="$(
33+
curl -fsSL "${WINE_LINK}" \
34+
| grep -oE "wine-${WINE_BRANCH}-amd64_[0-9][0-9.]*~${WINE_DIST}(-[0-9]+)?_amd64\.deb" \
35+
| sed -E "s/^wine-${WINE_BRANCH}-amd64_([0-9.]+~${WINE_DIST}(-[0-9]+)?)_amd64\.deb$/\1/" \
36+
| awk -F. -v m="${MAJOR}" '$1==m' \
37+
| sort -V | tail -1
38+
)"
39+
if [ -z "${WINE_BUILD}" ]; then
40+
echo "Failed to resolve WINE_BUILD for ${BUILD} (dist=${WINE_DIST})." >&2
41+
exit 2
42+
fi
43+
44+
apt-get -s install -y -o APT::Install-Recommends=1 \
45+
"wine-${WINE_BRANCH}-i386=${WINE_BUILD}" \
46+
"wine-${WINE_BRANCH}-amd64=${WINE_BUILD}" \
47+
"wine-${WINE_BRANCH}=${WINE_BUILD}" \
48+
"winehq-${WINE_BRANCH}=${WINE_BUILD}" \
49+
| _print_inst
50+
;;
51+
52+
*)
53+
echo "Unknown build: ${BUILD}" >&2
54+
exit 1
55+
;;
9056
esac
91-
92-
dedup() {
93-
awk '!seen[$0]++ && NF'
94-
}
95-
96-
# Build token set (explode alts, strip versions/quals)
97-
TOKENS="$(mktemp)"
98-
cat /tmp/wine-reqs.amd64 /tmp/wine-reqs.i386 \
99-
| awk 'BEGIN{RS="|"}{gsub(/^[ \t]+|[ \t]+$/,""); if(length)print}' \
100-
| sed -E 's/\(.*\)//; s/:any$//; s/:all$//; s/^[[:space:]]+|[[:space:]]+$//g' \
101-
| awk -F: '{print $1}' | dedup >"${TOKENS}"
102-
103-
# Availability caches (store base names for both arches)
104-
AVN="$(mktemp)"; : >"${AVN}"
105-
AVC="$(mktemp)"; : >"${AVC}"
106-
107-
# Native availability
108-
xargs -a "${TOKENS}" -r -I{} printf '%s:%s\n' '{}' "${NATIVE}" \
109-
| xargs -r apt-cache policy -- 2>/dev/null \
110-
| awk '
111-
/^[^ ]+:$/ {p=$1; sub(/:$/,"",p); next}
112-
/^ Candidate: / && $3!="(none)" {
113-
base=p; sub(/:.*/,"",base); print base
114-
}' | sort -u >"${AVN}"
115-
116-
# Compat availability
117-
if [ -n "${COMPAT}" ]; then
118-
xargs -a "${TOKENS}" -r -I{} printf '%s:%s\n' '{}' "${COMPAT}" \
119-
| xargs -r apt-cache policy -- 2>/dev/null \
120-
| awk '
121-
/^[^ ]+:$/ {p=$1; sub(/:$/,"",p); next}
122-
/^ Candidate: / && $3!="(none)" {
123-
base=p; sub(/:.*/,"",base); print base
124-
}' | sort -u >"${AVC}"
125-
fi
126-
127-
# Helper: pick first available alternative against given availability cache
128-
pick_alt() { line=$1; avail=$2; suff=${3:-}; OLDIFS=${IFS}; IFS='|'
129-
for t in ${line}; do
130-
p=$(printf '%s' "${t}" | sed -E 's/\(.*\)//; s/:any$//; s/:all$//; s/^[[:space:]]+|[[:space:]]+$//g')
131-
[ -n "${p}" ] || continue
132-
if grep -Fxq "${p}" "${avail}"; then
133-
[ -n "${suff}" ] && { printf '%s:%s\n' "${p}" "${suff}"; IFS=${OLDIFS}; return 0; }
134-
printf '%s\n' "${p}"; IFS=${OLDIFS}; return 0
135-
fi
136-
done
137-
IFS=${OLDIFS}; return 1
138-
}
139-
140-
# Helper: return 0 if pkg:arch is Multi-Arch: same, else 1
141-
ma_same_pkg() { # $1=pkg $2=arch
142-
apt-cache show "$1:$2" 2>/dev/null \
143-
| awk -v p="$1" -v a="$2" -F': ' '
144-
# parse stanza-by-stanza
145-
NF==0 { if (pk && ar==a && tolower(ma)=="same") {ok=1} pk=ar=ma=""; next }
146-
$1=="Package" { pk=$2 }
147-
$1=="Architecture" { ar=$2 }
148-
$1=="Multi-Arch" { ma=$2 }
149-
END { if (pk && ar==a && tolower(ma)=="same") ok=1; exit (ok?0:1) }
150-
'
151-
}
152-
153-
# Native → print bare names (stable de-dupe)
154-
NSET="$(mktemp)"; : >"${NSET}"
155-
while IFS= read -r L; do
156-
sel="$(pick_alt "${L}" "${AVN}" || true)"; [ -n "${sel}" ] || continue
157-
if ! grep -Fxq "${sel}" "${NSET}"; then printf '%s\n' "${sel}"; printf '%s\n' "${sel}" >>"${NSET}"; fi
158-
done < /tmp/wine-reqs.amd64
159-
160-
# Compat → pick vs compat cache; avoid native clash unless MA:same; print with :arch
161-
if [ -n "${COMPAT}" ]; then
162-
while IFS= read -r L; do
163-
first="$(pick_alt "${L}" "${AVC}" "${COMPAT}" || true)"; [ -n "${first}" ] || continue
164-
base="${first%:*}"
165-
166-
chosen=""
167-
if ! grep -Fxq "${base}" "${NSET}"; then
168-
chosen="${first}"
169-
else
170-
if ma_same_pkg "${base}" "${COMPAT}"; then
171-
chosen="${first}"
172-
else
173-
OLDIFS=${IFS}; IFS='|'
174-
for t in ${L}; do
175-
p=$(printf '%s' "${t}" | sed -E 's/\(.*\)//; s/:any$//; s/:all$//; s/^[[:space:]]+|[[:space:]]+$//g')
176-
[ -n "${p}" ] || continue
177-
grep -Fxq "${p}" "${AVC}" || continue
178-
if ! grep -Fxq "${p}" "${NSET_SORT}" || ma_same_pkg "${p}" "${COMPAT}"; then
179-
chosen="${p}:${COMPAT}"; break
180-
fi
181-
done
182-
IFS=${OLDIFS}
183-
fi
184-
fi
185-
186-
[ -n "${chosen}" ] && printf '%s\n' "${chosen}"
187-
done < /tmp/wine-reqs.i386 | dedup
188-
fi
189-
190-
# cleanup
191-
rm -f "${TOKENS}" "${AVN}" "${AVC}" "${NSET}" 2>/dev/null || true
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#!/bin/sh
2+
3+
set -eu
4+
5+
NATIVE="${NATIVE:-arm64}"
6+
COMPAT="${COMPAT:-armhf}"
7+
8+
# Make sure indexes for both arches exist for availability checks
9+
dpkg --add-architecture "${COMPAT}" >/dev/null 2>&1 || true
10+
apt-get -q -o Acquire::Languages=none update >/dev/null
11+
12+
# Helper dedupe
13+
seen="$(mktemp)"; trap 'rm -f "$seen"' INT TERM EXIT
14+
emit() {
15+
s=$1
16+
if ! grep -Fxq "$s" "${seen}" 2>/dev/null; then
17+
printf '%s\n' "${s}"
18+
printf '%s\n' "${s}" >>"${seen}"
19+
fi
20+
}
21+
22+
has_candidate() {
23+
# $1 = token ("pkg" or "pkg:arch"); returns 0 if APT has a candidate
24+
cand=$(
25+
apt-cache policy "$1" 2>/dev/null \
26+
| sed -n 's/^[[:space:]]\{1,\}Candidate: //p' | head -1
27+
)
28+
[ -n "${cand:-}" ] && [ "${cand}" != "(none)" ]
29+
}
30+
31+
# Read each line as a token "pkg[:arch]"
32+
while IFS= read -r tok; do
33+
# trim whitespace and CR
34+
tok=${tok%$'\r'}
35+
tok=$(printf '%s' "${tok}" | sed 's/^[[:space:]]\+//; s/[[:space:]]\+$//')
36+
[ -z "${tok}" ] && continue
37+
case "${tok}" in \#*) continue ;; esac
38+
39+
pkg=${tok%%:*}
40+
if [ "${pkg}" = "${tok}" ]; then
41+
suf=""
42+
else
43+
suf=${tok#*:}
44+
fi
45+
46+
# no suffix/amd64 => arm64 ; i386 => armhf ; other arches => skip
47+
case "${suf}" in
48+
""|"amd64") tgt="${NATIVE}" ;;
49+
"i386") tgt="${COMPAT}" ;;
50+
*) continue ;;
51+
esac
52+
53+
if has_candidate "${pkg}:${tgt}"; then
54+
emit "${pkg}:${tgt}"
55+
elif has_candidate "${pkg}"; then # covers Architecture: all
56+
emit "${pkg}"
57+
fi
58+
done

0 commit comments

Comments
 (0)