summaryrefslogblamecommitdiff
path: root/scripts/update
blob: f1f02c238b911cc3cdd234f79611daaac7e5e161 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500



















































































































































































































































































































































































































































































































                                                                                                                           
#!/bin/sh -e

HERE="$(dirname $(readlink -f "${0}"))";
TEMP="$(mktemp -d)";
cd "${HERE}"/..; # repository root (do not touch!)

##
# README
#
# This script will determine which package(s) need to be
# built or rebuilt by evaluating the state of mirrormaster
# (or "next.adelielinux.org" in the case of a staging box)
# and comparing it to the state of an official builder.
#
# It is to be run only on an official builder by someone
# with access to and training to operate such a machine,
# which may include automation and robots.
#
# This script assumes it runs inside an autobuilder 'debug'
# environment. This is not an optional requirement. This is
# an internal tool not intended for public-facing usage.
#
# This script assumes "next" ('NEXT') and "ours" ('OURS') are
# synchronized. That is, binaries at the two locations are
# either the same, or point to the same location. This is to
# warn the operator that mirrormaster might be out of sync
# with builders, and is not useful for developers.


##
# TODO
#
# When checking 'DESCRIPTION' from APKINDEX, this is NOT
# a reliable way to see what is *actually* the last package
# built, and from what commit hash. This will cause issues!
#
# The computed list is too broad, specifically the 'comm -23'
# line on 'list.*.sort' files.
#
# Register trap handlers to clean up and restore backups
# appropriately; biggest risk is inconsistent state. Second
# thing to avoid is wasted CPU time or having to restart.
#
# Implement build logs as in normal autobuilder.
#
# Performance enhancements.
#
# Mathematically rigorous series of transformations to
# compute various bits here, not just a series of loops,
# lists, etc.
#


##
# Configuration (use defaults for production!).
#
NEXT=https://next.adelielinux.org;
OURS=/packages;
REPO="system user"; # public-facing repositories
ARCH="${AB_ARCH}"; # from autobuilder environment
BNCH=current; # probably do not change this


##
# Helpers.
#
message ()
{
    printf "  * %s...\n" "${1}";
}

fetch_local ()
{
    cat "${1}";
}
fetch_curly ()
{
    curl -sL "${1}";
}

find_strategy ()
{
    case "${1}" in
        /*)    down=fetch_local; ;; # local filesystem
        *://*) down=fetch_curly; ;; # remote resource
        *)     printf "E: unsupported site type!\n" && exit 1; ;;
    esac

    printf "%s\n" "${down}";
}

##
# Counts number of commits between two commits (A,B]
#
# LAST: latest commit available (e.g. origin/current)
# FROM: commit to compare
#
count_behind ()
{
    f_last="${1}"; shift;
    f_from="${1}"; shift;

    git log --format="%H" ${f_from}..${f_last} | wc -l;
}

##
# Manually update (if greater than global) to work around
# a shell limitation in updating globals from subshells.
#
# NOTE: USES GLOBAL.
#
count_update ()
{
    f_this="${1}"; shift;

    # store the newest maximum
    if test "${OLD_this}" -lt "${f_this}"; then
        OLD_this=${f_this};
    fi

    if test "${OLD_keep}" -lt "${f_this}"; then
        OLD_keep=${f_this};
    fi
}

##
# String comparison, but with more screaming.
#
check_match ()
{
    f_next="${1}"; shift;
    f_ours="${1}"; shift;
    f_repo="${1}"; shift;

    # OK if ours is newer than next
    hind=$(count_behind "${f_next}" "${f_ours}");
    test "${hind}" -eq 0 && return;

    printf "E: "next" ('%s') != "ours" ('%s') in repo '%s'!\n" "${f_next}" "${f_ours}" "${f_repo}";
    exit 1;
}

##
# Produce a sorted list of files in a given ON-DISK
# binary repository. TODO: update requirements so that
# the "ours" repository is always required to be on-disk.
#
list_packages ()
{
    f_repo="${1}"; shift;

    find "${OURS}"/${f_repo}/${ARCH} | sort;
}


##
# Sanity checks.
#
message "Verifying configuration and environment";
test -z ${ARCH} && printf "E: 'ARCH' could not be determined!\n" && exit 1;
command -v curl >/dev/null 2>&1 || (printf "E: required utility 'curl' not found!\n" && exit 1);


##
# Internal.
#
message "Identifying transfer strategies";
_NEXT=$(find_strategy "${NEXT}");
_OURS=$(find_strategy "${OURS}");


##
# Update source code, if needed.
#
message "Updating local source repository"
git checkout "${BNCH}";
git pull --ff-only;


##
# Do it.
#
# Note: underscore-prefixed variables refer to friendly names.
# Sometimes these variables need to come before, or after, due
# to whether we use the friendly name to calculate commit, or
# the other way around. This is intentional and ugly. Sorry.
#
print_status ()
{
    f_code="${1}"; shift;
    f_kind="${1}"; shift;
    f_repo="${1}"; shift;
    f_hash="${1}"; shift;
    f_many="${1}"; shift;
    f_name="${1}"; shift;

    printf "      - [%s] %s (%s)\tis at %s [-%4d] (%s)...\n" \
        "${f_code}" \
        "${f_kind}" \
        "${f_repo}" \
        "${f_hash}" \
        "${f_many}" \
        "${f_name}" \
        ;
}
message "Examining repositories";
meta=$(git log --format="%H" . | head -n 1);
_meta=$(git describe ${meta});
nmeta=$(count_behind "${meta}" "${meta}"); # redundant i know, but consistent :D
print_status REF .git meta "${meta}" "${nmeta}" "${_meta}";
# source of truth
OLD_list=""; # starts empty, CURRENTLY UNUSED but probably useful!
OLD_keep=0; # global maximum
for repo in ${REPO}; do
    OLD_this=0; # reset zero at each iteration

    # disk
    disk=$(git log --format="%H" ${repo} | head -n 1); # last commit in repository on disk
    _disk=$(git describe "${disk}");
    ndisk=$(count_behind "${meta}" "${disk}");
    count_update "${ndisk}";
    print_status SRC .git "${repo}" "${disk}" "${ndisk}" "${_disk}";

    # next
    ${_NEXT} ${NEXT}/${repo}/${ARCH}/APKINDEX.tar.gz | tar -C "${TEMP}" -xzf - DESCRIPTION;
    _next=$(cut -d\  -f2 < "${TEMP}"/DESCRIPTION);
    next=$(git log --format="%H" "${_next}" | head -n 1);
    nnext=$(count_behind "${meta}" "${next}");
    count_update "${nnext}";

    # ours
    ${_OURS} ${OURS}/${repo}/${ARCH}/APKINDEX.tar.gz | tar -C "${TEMP}" -xzf - DESCRIPTION;
    _ours=$(cut -d\  -f2 < "${TEMP}"/DESCRIPTION);
    ours=$(git log --format="%H" "${_ours}" | head -n 1);
    nours=$(count_behind "${meta}" "${ours}");
    count_update "${nours}";

    # do they match (fatal if not)?
    check_match "${_next}" "${_ours}" "${repo}"; # could also check commit or count but this is direct

    # cool, print current status
    print_status BIN next "${repo}" "${next}" "${nnext}" "${_next}";
    print_status BIN ours "${repo}" "${ours}" "${nours}" "${_ours}";

    # save per-repository freshness (DO NOT CHANGE STORAGE ORDER!)
    OLD_list="${OLD_list} ${repo}:${OLD_this}";
done


message "Generating local repository index"
./scripts/setup;


message "Calculating topological package build order"
temp=$(mktemp);
./scripts/deplist ${REPO} | ./scripts/depsort | cat -n > ${temp};


message "Calculating initial build requirements"
LIST=$(git log --pretty=format:"%h" HEAD~${OLD_keep}..HEAD | while read commit; do
    git diff-tree --no-commit-id --name-only -r ${commit};
done | grep APKBUILD | cut -d/ -f-2 | sort | uniq | while read k; do
    grep -E "${k}$" ${temp};
done | sort -n | awk '{print $2}');
rm ${temp};


message "Going back in time by ${OLD_keep} commits";
git checkout "${BNCH}"~"${OLD_keep}";
for k in old new bin cmp; do
    rm -fr "${TEMP}"/${k};
    mkdir  "${TEMP}"/${k};
done

message "Tracking starting state"
for item in ${LIST}; do
(
    test -f "${item}"/APKBUILD || continue; # could be new package
    cd "${item}";
    . ./APKBUILD;
    printf > "${TEMP}"/old/${item%/*}.${item#*/} "pkgname=%s\npkgver=%s\npkgrel=%s\n" "${pkgname}" "${pkgver}" "${pkgrel}";
)
done

message "Going forward in time to present";
git checkout "${BNCH}";
for item in ${LIST}; do
(
    test -f "${item}"/APKBUILD || continue; # could be deleted package
    cd "${item}";
    . ./APKBUILD;
    printf > "${TEMP}"/new/${item%/*}.${item#*/} "pkgname=%s\npkgver=%s\npkgrel=%s\n" "${pkgname}" "${pkgver}" "${pkgrel}";
)
done

message "Verifying ALL pkgver/pkgrel are different";
FAIL=0;
for item in ${LIST}; do
(
    if ! test -f "${TEMP}"/old/${item%/*}.${item#*/} || ! test -f "${TEMP}"/new/${item%/*}.${item#*/}; then
        continue; # not in both
    fi

    if ! cmp --silent "${TEMP}"/old/${item%/*}.${item#*/} "${TEMP}"/old/${item%/*}.${item#*/}; then
        printf "E: package '%s' needs a relbump!\n" "${item}";
        FAIL=1;
        continue;
    fi

if false; then
    . "${TEMP}"/old/${item%/*}.${item#*/};
    old_pkgver=$pkgver;
    old_pkgrel=$pkgrel;

    . "${TEMP}"/new/${item%/*}.${item#*/};
    new_pkgver=$pkgver;
    new_pkgrel=$pkgrel;

    # TODO: actually compare versions?? or not necessary??
fi
)
done
# THIS PROPERTY IS VERY IMPORTANT!! DO NOT PROCEED IF ANY PACKAGES WOULD BE CLOBBERED!
test "${FAIL}" -eq 1 && printf "E: not all package versions are good (would clobber)!\n" && exit 1;


message "Generating backup list of local package binaries"
for repo in ${REPO}; do
    list_packages ${repo} > "${TEMP}"/manifest.${repo}.a;
done


message "Backing up APKINDEX files";
for repo in ${REPO}; do
    cp -p "${OURS}"/${repo}/${ARCH}/APKINDEX.tar.gz "${TEMP}"/APKINDEX.tar.gz.${repo}.bak;
done


message "Building packages for TESTING (STILL DANGEROUS!)"
for item in ${LIST}; do
(
    cd "${item}";

    # save state of binary package repository before build
    list_packages ${item%/*} > "${TEMP}"/build.a;

    # do the build and hope nothing gets clobbered!
    # TODO: can this be replaced by 'BOOTSTRAP=1 abuild -r'? do we need full control?
    # the current command does not clean up any files (like '-K')
    abuild sanitycheck clean deps unpack prepare mkusers build rootpkg index undeps;

    # save state of binary package repository  after build
    list_packages ${item%/*} > "${TEMP}"/build.b;

    # compute delta and store to a file for later use
    comm -13 "${TEMP}"/build.a "${TEMP}"/build.b > "${TEMP}"/bin/${item%/*}.${item#*/};
)
done


message "Moving newly-built packages to a less scary directory"
for item in ${LIST}; do
(
    cd "${item}";

    # get those toxic critters outta' here!
    while read k; do
        mv "${k}" "${TEMP}"/cmp/;
    done < "${TEMP}"/bin/${item%/*}.${item#*/};
)
done


message "Clearing any stale cached files"
for k in a b; do
    rm -f "${TEMP}"/list.${k};
done


##
# NOTE: we CANNOT assume that there aren't multiple older versions
# of a given package in "${OURS}", while we only care about the
# "current" (latest) version that existed prior to the above build.
#
message "Scanning packages for shared library changes"
for item in ${LIST}; do
(
    cd "${item}";

    # get sorted list of .so files in all old .apk files for this package
    test ! -f "${TEMP}"/old/${item%/*}.${item#*/} && continue; # skip because it's new!
    (
        . "${TEMP}"/old/${item%/*}.${item#*/};

        find "${OURS}"/${item%/*}/${ARCH} -type f -name "${pkgname}-*${pkgver}-r${pkgrel}.apk" | while read k; do
            tar -tzf "${k}";
        done | grep \\.so | sort >> "${TEMP}"/list.a;
    )

    # get sorted list of .so files in all new .apk files for this package
    # FIXME: what happens if a package is deleted? still shows up in 'LIST' or not?
    (
        . "${TEMP}"/new/${item%/*}.${item#*/};

        find "${TEMP}"/cmp -type f -name "${pkgname}-*${pkgver}-r${pkgrel}.apk" | while read k; do
            tar -tzf "${k}";
        done | grep \\.so | sort >> "${TEMP}"/list.b;
    )
)
done


##
# FIXME: this needs to be done anyway, but this step is ALSO
# a shortcut/kludge to avoid manually updating the index files
# after moving the newly-built binaries out of "${OURS}".
# After this step, there should be no visible before/after.
#
# NOTE: this is a full swap so that we also have a copy of
# the "new" APKINDEX for later analysis.
#
message "Restoring original APKINDEX files";
for repo in ${REPO}; do
    cp -p "${OURS}"/${repo}/${ARCH}/APKINDEX.tar.gz "${TEMP}"/APKINDEX.tar.gz.${repo}.tmp;
    mv "${TEMP}"/APKINDEX.tar.gz.${repo}.bak "${OURS}"/${repo}/${ARCH}/APKINDEX.tar.gz;
    mv "${TEMP}"/APKINDEX.tar.gz.${repo}.tmp "${TEMP}"/APKINDEX.tar.gz.${repo};
done


message "Sorting list of changed shared libraries"
for k in a b; do
    sort "${TEMP}"/list.${k} | uniq > "${TEMP}"/list.${k}.sort;
done


##
# NOTE: the '-q' to 'apk' gives us package names without
# version information. It may be possible for multiple
# versions to exist, and only one be affected, and that only
# the latest one could be affected, and that we already store
# the "old" version information, ... but it doesn't matter
# anyway since that information is derived from a binary repo
# and we only care about the source repo from here forward.
#
# The '-q' also removes the "blah is required by..." line.
#
# TODO: don't actually call 'apk' here (slow); parse the
# APKINDEX data and go from there.
#
message "Tracing dependencies for each changed shared library"
comm -23 "${TEMP}"/list.a.sort "${TEMP}"/list.b.sort | while read file; do
    apk search -rq so:${file##*/};
done > "${TEMP}"/deps


message "Finding parents of each affected package"
sort "${TEMP}"/deps | while read name; do
    grep ${name} scripts/.index; # generated by 'setup'
done | awk '{print $1}' | sort | uniq > "${TEMP}"/parents;


##
# TODO: since we cannot know which repository(ies) provide the
# possibly-affected package(s), we must search all repositories
# for all occurrences.
#
message "Finding all APKBUILD files for each parent"
NEED="";
while read name; do
    for repo in ${REPO}; do
        if test -f ${repo}/${name}/APKBUILD; then
            NEED="${NEED} ${repo}/${name}";
        fi
    done    
done < "${TEMP}"/parents;



message "Removing any duplicate entries"
printf "%s\n" "${LIST}" | sort > "${TEMP}"/LIST;
printf "%s\n" "${NEED}" | tr ' ' '\n' | sort > "${TEMP}"/NEED;
comm -13 "${TEMP}"/LIST "${TEMP}"/NEED;

echo $TEMP;


message "Generating change list of local package binaries"
for repo in ${REPO}; do
    list_packages ${repo} > "${TEMP}"/manifest.${repo}.b;
done


message "Comparing backup and change lists (sanity check)"
for repo in ${REPO}; do
    diff -ur "${TEMP}"/manifest.${repo}.a "${TEMP}"/manifest.${repo}.b;
done


message "Cleaning up"
#rm -fr "${TEMP}";