#!/bin/sh
# Regenerate the Debian orig tarball for dart-sdk from upstream.
#
# The Dart SDK is not distributed as a single upstream tarball: the sdk.git
# tree pulls its third_party/ dependencies from separate repositories pinned by
# upstream's DEPS file, so the tarball has to be built from a gclient checkout.
# This script does that end to end, applies the Files-Excluded list from
# debian/copyright (the single source of truth for what is dropped), and writes
# a bit-for-bit reproducible tarball.
#
# Usage, from the root of the source package:
#     debian/scripts/get-orig-source [VERSION] [WORKDIR]
#
# VERSION is the Debian upstream version, e.g. 3.13.4+dfsg1; the git tag is
# derived from it by stripping the repack suffix. It defaults to the upstream
# version in debian/changelog and WORKDIR
# to ../dart-sdk-orig. Requires network access, git, xz and depot_tools
# (https://chromium.googlesource.com/chromium/tools/depot_tools.git) on PATH.
# The checkout is large (about 20 GB); the resulting tarball is about 150 MB.

set -eu

UPSTREAM_GIT=https://dart.googlesource.com/sdk.git

# The Debian upstream version carries a +dfsgN suffix because this tarball is
# repacked to drop content that is not DFSG free (see debian/copyright). The
# git tag to check out is that version with the suffix removed.
VERSION=${1:-}
if [ -z "$VERSION" ]; then
    VERSION=$(dpkg-parsechangelog -S Version | sed -e 's/^[0-9]*://' -e 's/-[^-]*$//')
fi
UPSTREAM_TAG=$(printf '%s' "$VERSION" | sed -E 's/\+(dfsg|ds)[0-9]*$//')
WORKDIR=${2:-../dart-sdk-orig}

if [ ! -f debian/copyright ]; then
    echo "$0: run me from the root of the dart-sdk source package" >&2
    exit 1
fi
if ! command -v gclient >/dev/null 2>&1; then
    echo "$0: gclient not found; put depot_tools on PATH" >&2
    exit 1
fi

# The exclusion list lives in debian/copyright's Files-Excluded field so that
# the tarball and the copyright file can never drift apart.
EXCLUDES=$(awk '
    /^Files-Excluded:/ { sub(/^Files-Excluded:[[:space:]]*/, ""); if ($0 != "") print; inblock = 1; next }
    inblock && /^[[:space:]]+[^[:space:]]/ { sub(/^[[:space:]]+/, ""); print; next }
    inblock { exit }
' debian/copyright)

if [ -z "$EXCLUDES" ]; then
    echo "$0: no Files-Excluded field found in debian/copyright" >&2
    exit 1
fi

SOURCEDIR=$(pwd)
mkdir -p "$WORKDIR"
cd "$WORKDIR"
WORKDIR=$(pwd)

# 1. Upstream checkout at the requested tag, with DEPS-pinned third_party trees.
if [ ! -d sdk/.git ]; then
    git clone "$UPSTREAM_GIT" sdk
fi
git -C sdk fetch --tags --prune origin
git -C sdk checkout --detach "$UPSTREAM_TAG"

cat > .gclient <<'GCLIENT'
solutions = [
  { "name"        : 'sdk',
    "url"         : 'https://dart.googlesource.com/sdk.git',
    "deps_file"   : 'DEPS',
    "managed"     : False,
    "custom_deps" : {},
    "custom_vars" : {},
  },
]
GCLIENT
gclient sync --no-history

REVISION=$(git -C sdk rev-parse HEAD)
# Upstream's own commit date, so the tarball is reproducible.
SOURCE_DATE=$(git -C sdk log -1 --pretty=%cI)

# 2. Pristine, de-vendored tree.
DIST="dart-sdk-$VERSION"
rm -rf "$DIST"
# .git and gclient bookkeeping are not part of the source; __pycache__ is
# bytecode the gclient hooks leave behind when they run the in-tree python
# helpers, and must not end up in the tarball.
set -- \
    --exclude=.git \
    --exclude=.gclient_entries \
    --exclude=.gclient_previous_sync_commits \
    --exclude=__pycache__ \
    --exclude=*.pyc
for pattern in $EXCLUDES; do
    set -- "$@" --exclude="/$pattern"
done
rsync -a --delete "$@" sdk/ "$DIST"/

# Nothing excluded above may survive, and nothing prebuilt may be left behind.
for pattern in $EXCLUDES; do
    if [ -e "$DIST/$pattern" ]; then
        echo "$0: Files-Excluded entry still present: $pattern" >&2
        exit 1
    fi
done

# No compiled Python may survive either.
if find "$DIST" \( -name '*.pyc' -o -name __pycache__ \) -print -quit | grep -q .; then
    echo "$0: compiled Python left in the tarball" >&2
    exit 1
fi

# A directory removed above may be the root of a package listed in the
# generated package config. Leaving a dangling entry there would ship a config
# pointing at files that are not in the tarball, so prune those entries first;
# whatever is left must exist, which the check below enforces.
python3 - "$DIST" <<'PYTHON'
import json, os, sys

dist = sys.argv[1]
config = os.path.join(dist, '.dart_tool', 'package_config.json')
if os.path.exists(config):
    with open(config) as fh:
        data = json.load(fh)
    keep, pruned = [], []
    for package in data['packages']:
        root = os.path.normpath(os.path.join(dist, '.dart_tool', package['rootUri']))
        (keep if os.path.isdir(root) else pruned).append(package)
    if pruned:
        data['packages'] = keep
        with open(config, 'w') as fh:
            json.dump(data, fh, indent=2)
            fh.write('\n')
        print('package config: pruned %d excluded package(s): %s'
              % (len(pruned), ', '.join(p['name'] for p in pruned)))
PYTHON

# An exclusion must not take away a Dart package the SDK is built from. Some
# third_party directories mix a prebuilt bundle with real source next to it
# (third_party/devtools is both a 92 MB prebuilt web application and the
# devtools_shared library that pkg/dds imports), so check every package in the
# generated package config instead of trusting the directory name.
python3 - "$DIST" <<'PYTHON'
import json, os, sys

dist = sys.argv[1]
config = os.path.join(dist, '.dart_tool', 'package_config.json')
if not os.path.exists(config):
    sys.exit(0)
with open(config) as fh:
    packages = json.load(fh)['packages']
missing = []
for package in packages:
    root = os.path.normpath(os.path.join(dist, '.dart_tool', package['rootUri']))
    if not os.path.isdir(root):
        missing.append((package['name'], package['rootUri']))
if missing:
    for name, uri in missing:
        print('excluded a Dart package the build needs: %s (%s)' % (name, uri),
              file=sys.stderr)
    sys.exit(1)
print('package config: %d packages, all present' % len(packages))
PYTHON

# 3. Reproducible tarball: fixed order, fixed ownership, fixed timestamps.
TARBALL="$WORKDIR/dart-sdk_$VERSION.orig.tar.xz"
rm -f "$TARBALL"
tar --create \
    --sort=name \
    --owner=root:0 --group=root:0 --numeric-owner \
    --mtime="$SOURCE_DATE" \
    --format=gnu \
    -- "$DIST" \
    | xz -T0 -6 > "$TARBALL"

cat <<EOF

Upstream tag : $UPSTREAM_TAG
Revision     : $REVISION
Tarball      : $TARBALL
sha256       : $(sha256sum "$TARBALL" | cut -d' ' -f1)

Now copy the tarball next to the source tree and build, e.g.:
    cd $SOURCEDIR && dpkg-buildpackage -us -uc -S
EOF
