summaryrefslogtreecommitdiffstats
path: root/scripts/distro-manifest-generator.sh
blob: 0db3e132fa901b02593aa382834267edb338074f (plain)
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
#!/usr/bin/env bash

################################################################################
#
# The MIT License (MIT)
#
# Copyright (c) 2018 Stéphane Desneux <sdx@iot.bzh>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
################################################################################

mode=deploy
manifest=
verbose=0
format=bash
sourcefile=
timestamp="$(date -u +%Y%m%d_%H%M%S_%Z)"

function info() { echo "$@" >&2; }
function error() { echo "$BASH_SOURCE: $@" >&2; }
function out() { echo -n "$@"; }
function out_object() {
	# expected stdin stream is:
	# --------------
	# key
	# value
	# key
	# value
	# ...
	# --------------
	local sep=""
	local k
	case $format in
		bash)
			while read x; do
				[[ -z "$k" ]] && { k="$x"; continue; }
				out "$sep${k}="
				out_value "$x"
				sep=$'\n'
				k=
			done
			out "$sep"
			;;
		json)
			out "{"
			while read x; do
				[[ -z "$k" ]] && { k="$x"; continue; }
				out "$sep\"${k}\":"
				out_value "$x"
				sep=","
				k=
			done
			out "}"
			;;
	esac
}

function out_array() {
	# expected stdin stream is:
	# --------------
	# value
	# value
	# ...
	# --------------
	local sep=""
	case $format in
		bash)
			while read x; do
				out "$sep"
				out_value "$x"
				sep=" "
			done
			;;
		json)
			out "["
			while read x; do
				out $sep
				out_value "$x"
				sep=","
			done
			out "]"
			;;
	esac
}

function out_value() {
	# string
	# number
	# object
	# array
	# 'true'
	# 'false'
	# 'null'

	x=$1

	# litterals
	if [[ "$x" =~ ^(true|false|null)$ ]]; then
		out "$x"
	# number
	elif [[ "$x" =~ ^[+-]?[0-9]+(\.[0-9]+)?$ ]]; then
		out "$x"
	# object
	elif [[ "$x" =~ ^\{.*\}$ ]]; then
		out "$x"
	# array
	elif [[ "$x" =~ ^\[.*\]$ ]]; then
		out "$x"
	# string
	else
		out "\"$(sed 's/\("\)/\\\1/g' <<<$x)\""
	fi
}

function out_comment() {
	case $format in
		bash)
			[[ "$verbose" == 1 ]] && echo "# $@" || true
			;;
		json)
			;;
	esac
}

function _getgitmanifest() {
	# this function takes the setup.manifest generated by setup script and uses DIST_METADIR
	# to analyze git repos

	local manifest=$1 mode=$2
	[[ -f $manifest ]] && source $manifest || { error "Invalid setup manifest '$manifest'"; return 1; }
	[[ ! -d "$DIST_METADIR" ]] && {
		error "Invalid meta directory. Check variable DIST_METADIR in manifest file '$manifest'."
		error "$BASH_SOURCE: Also, check directory '$DIST_METADIR'."
		return 2
	}
	local GIT=$(which git) REALPATH=$(which realpath)
	[[ ! -x $GIT ]] && { error "$BASH_SOURCE: Unable to find git command in $PATH."; return 3; }
	[[ ! -x $REALPATH ]] && { error "$BASH_SOURCE: Unable to find realpath command in $PATH."; return 4; }

	local gitrepo gitrev metagitdir sep=""
	DIST_LAYERS=""
	for metagitdir in $(find $DIST_METADIR -maxdepth 2 -type d \( -not -path '*/.*' \) -exec test -d "{}/.git" \; -print -prune); do
		gitrepo=$($REALPATH -Ls $metagitdir --relative-to=$DIST_METADIR)
		pushd $DIST_METADIR/$gitrepo &>/dev/null && {
			gitrev=$( { $GIT describe --long --dirty --always 2>/dev/null || echo "unknown_revision"; } | tr ' \t' '__' )
			popd &>/dev/null
		} || {
			gitrev=unknown
		}
		DIST_LAYERS="${DIST_LAYERS}${sep}${gitrepo}:${gitrev}"
		sep=" "
	done

	# layers checksum
	DIST_LAYERS_MD5=$(echo $DIST_LAYERS|md5sum -|awk '{print $1;}')

	# in json, transform layers in an object, features in array
	[[ "$format" == "json" ]] && {
		DIST_FEATURES=$(for x in $DIST_FEATURES; do
			echo $x
		done | out_array)
		DIST_LAYERS=$(for x in $DIST_LAYERS; do
			echo ${x%%:*}
			echo ${x#*:}
		done | out_object)
	}


	# compute build hash
	DIST_BUILD_HASH="F${DIST_FEATURES_MD5:0:8}-L${DIST_LAYERS_MD5:0:8}"
	DIST_BUILD_ID="${DIST_DISTRO_NAME}-${DIST_MACHINE}-F${DIST_FEATURES_MD5:0:8}-L${DIST_LAYERS_MD5:0:8}"


	# compute setup manifest path and build TS
	DIST_SETUP_MANIFEST="$($REALPATH $manifest)"

	# Manifest build timestamp
	DIST_BUILD_TS="$timestamp"

	# build topic from setup topic
	DIST_BUILD_TOPIC="${DIST_SETUP_TOPIC}"

	# what to retain from setup manifest?
	# to generate the full list: cat setup.manifest  | grep = | cut -f1 -d"=" | awk '{printf("%s ",$1);}'
	declare -A SETUP_VARS
	SETUP_VARS[deploy]="DIST_MACHINE DIST_FEATURES DIST_FEATURES_MD5 DIST_BUILD_HOST DIST_BUILD_OS DIST_SETUP_TS"
	SETUP_VARS[target]="DIST_MACHINE DIST_FEATURES"
	SETUP_VARS[sdk]="DIST_MACHINE DIST_FEATURES"

	# extra vars not coming from setup.manifest but generated here
	declare -A EXTRA_VARS
	EXTRA_VARS[deploy]="DIST_SETUP_MANIFEST DIST_BUILD_TS DIST_LAYERS DIST_LAYERS_MD5 DIST_BUILD_HASH DIST_BUILD_ID DIST_BUILD_TOPIC"
	EXTRA_VARS[target]="DIST_LAYERS DIST_BUILD_HASH DIST_BUILD_ID DIST_BUILD_TS DIST_BUILD_TOPIC"
	EXTRA_VARS[sdk]="DIST_LAYERS DIST_BUILD_HASH DIST_BUILD_ID DIST_BUILD_TS DIST_BUILD_TOPIC"

	# BITBAKE_VARS may be defined from external file to source (--source arg)
	# this is used to dump extra vars from inside bitbake recipe

	{ for x in ${SETUP_VARS[$mode]} ${EXTRA_VARS[$mode]} ${BITBAKE_VARS[$mode]}; do
		k=$x
		[[ "$format" == "json" ]] && {
			k=${k#DIST_} # remove prefix
			k=${k,,*} # to lower case
		}
		echo $k
		echo ${!x}
	done } | out_object
}

function getmanifest() {
	local rc=0
	out_comment "DISTRO BUILD MANIFEST"
	out_comment

	# add layers manifest
	out_comment "----- this fragment has been generated by $BASH_SOURCE"
	_getgitmanifest $1 $2 || rc=$?
	out_comment "------------ end of $BASH_SOURCE fragment --------"

	return $rc
}

function __usage() {
	cat <<EOF >&2
Usage: $BASH_SOURCE [-v|--verbose] [-f|--format <fmt>] [-t|--timestamp <value>] [-m|--mode <mode>] [-s|--source <file>] <setup_manifest_file>
   Options:
      -v|--verbose: generate comments in the output file
      -s|--source: extra file to source (get extra variables generated from bitbake recipe)
      -t|--timestamp: set build timestamp (default: current date - may not be the same ts as bitbake)
      -f|--format: specify output format: 'bash' or 'json'
      -m|--mode: specify the destination for the generated manifest
         'deploy' : for the tmp/deploy/images/* directories
         'target' : for the manifest to be installed inside a target image
         'sdk'    : for the manifest to be installed inside the SDK

   <setup_manifest_file> is the input manifest generated from setup script
EOF
}

set -e

tmp=$(getopt -o h,v,m:,f:,t:,s: --long help,verbose,mode:,format:,timestamp:,source: -n "$BASH_SOURCE" -- "$@") || {
	error "Invalid arguments."
	__usage
	exit 1
}
eval set -- $tmp

while true; do
	case "$1" in
		-h|--help) __usage; exit 0;;
		-v|--verbose) verbose=1; shift ;;
		-f|--format) format=$2; shift 2;;
		-m|--mode) mode=$2; shift 2;;
		-t|--timestamp) timestamp=$2; shift 2;;
		-s|--source) sourcefile=$2; shift 2;;
		--) shift; break;;
		*) fatal "Internal error";;
	esac
done

manifest=$1
shift
[[ ! -f "$manifest" ]] && { __usage; exit 1; }

case $mode in
	deploy|target|sdk) ;;
	*) error "Invalid mode specified. Allowed modes are: 'deploy', 'target', 'sdk'"; __usage; exit 42;;
esac

case $format in
	bash|json) ;;
	*) error "Invalid format specified. Allowed formats are 'json' or 'bash'"; __usage; exit 43;;
esac

info "Generating manifest: mode=$mode format=$format manifest=$manifest"
[[ -f "$sourcefile" ]] && {
	info "Sourcing file $sourcefile"
	. $sourcefile
	# this may define extra vars: to be taken into account BITBAKE_VARS must be defined
}

[[ "$format" == "json" ]] && {
	# if jq is present, use it to format json output
	jq=$(which jq || true)
	[[ -n "$jq" ]] && {
		getmanifest $manifest $mode | $jq ""
		exit ${PIPESTATUS[0]}
	}
}

getmanifest $manifest $mode
` -gt 1 ] && die "Image mounted more than once, manual cleaning required see: losetup --list" debug "ready to attach the wic image to aloop device" LOOP_DEVICE=`losetup --find --show $HDDIMG` && ( losetup -d $LOOP_DEVICE 1>&3 2>&1 || die "Detaching $LOOP_DEVICE from $HDDIMG failled") ;; *) die "unknown image format $IMG_TYPE" ;; esac # # Check if any $DEVICE partitions are mounted # unmount_device || die "Failed to unmount $DEVICE" # # Confirm device with user # image_details $HDDIMG device_details $(basename $DEVICE) echo -n "${INFO}Prepare ABL image on $DEVICE [y/N]?${CLEAR} " read RESPONSE if [ "$RESPONSE" != "y" ]; then echo "Image creation aborted" exit 0 fi # # Prepare the temporary working space # TMPDIR=$(mktemp -d mkabldisk-XXX) || die "Failed to create temporary mounting directory." HDDIMG_MNT=$TMPDIR/hddimg debug "TEMPDIR is: $TMPDIR" HDDIMG_ROOTFS_MNT=$TMPDIR/hddimg_rootfs ROOTFS_MNT=$TMPDIR/rootfs BOOTFS_MNT=$TMPDIR/bootfs mkdir $HDDIMG_MNT || die "Failed to create $HDDIMG_MNT" mkdir $HDDIMG_ROOTFS_MNT || die "Failed to create $HDDIMG_ROOTFS_MNT" mkdir $ROOTFS_MNT || die "Failed to create $ROOTFS_MNT" mkdir $BOOTFS_MNT || die "Failed to create $BOOTFS_MNT" # # Partition $DEVICE # DEVICE_SIZE=$(parted -s $DEVICE unit mb print | grep ^Disk | cut -d" " -f 3 | sed -e "s/MB//") # If the device size is not reported there may not be a valid label if [ "$DEVICE_SIZE" = "" ] ; then parted -s $DEVICE mklabel msdos || die "Failed to create MSDOS partition table" DEVICE_SIZE=$(parted -s $DEVICE unit mb print | grep ^Disk | cut -d" " -f 3 | sed -e "s/MB//") fi ROOTFS_SIZE=$((DEVICE_SIZE-BOOT_SIZE)) ROOTFS_START=$((BOOT_SIZE)) ROOTFS_END=$((ROOTFS_START+ROOTFS_SIZE)) # MMC devices use a partition prefix character 'p' PART_PREFIX="" if [ ! "${DEVICE#/dev/mmcblk}" = "${DEVICE}" ] || [ ! "${DEVICE#/dev/loop}" = "${DEVICE}" ]; then PART_PREFIX="p" fi BOOTFS=$DEVICE${PART_PREFIX}1 ROOTFS=$DEVICE${PART_PREFIX}2 TARGET_PART_PREFIX="" if [ ! "${TARGET_DEVICE#/dev/mmcblk}" = "${TARGET_DEVICE}" ]; then TARGET_PART_PREFIX="p" fi TARGET_ROOTFS=$TARGET_DEVICE${TARGET_PART_PREFIX}2 echo "" info "Boot partition size: $BOOT_SIZE MB ($BOOTFS)" info "ROOTFS partition size: $ROOTFS_SIZE MB ($ROOTFS)" echo "" # Use MSDOS by default as GPT cannot be reliably distributed in disk image form # as it requires the backup table to be on the last block of the device, which # of course varies from device to device. info "Partitioning installation media ($DEVICE)" debug "Deleting partition table on $DEVICE" dd if=/dev/zero of=$DEVICE bs=512 count=2 1>&3 2>&1 || die "Failed to zero beginning of $DEVICE" debug "Creating new partition table (MSDOS) on $DEVICE" parted -s $DEVICE mklabel msdos 1>&3 2>&1 || die "Failed to create MSDOS partition table" debug "Creating boot partition on $BOOTFS" parted -s $DEVICE mkpart primary 0% $BOOT_SIZE 1>&3 2>&1 || die "Failed to create BOOT partition" debug "Enabling boot flag on $BOOTFS" parted -s $DEVICE set 1 boot on 1>&3 2>&1 || die "Failed to enable boot flag" debug "Creating ROOTFS partition on $ROOTFS" parted -s $DEVICE mkpart primary $ROOTFS_START $ROOTFS_END 1>&3 2>&1 || die "Failed to create ROOTFS partition" # as blkid does not provide PARTUUID on Ubuntu LTS 14.04 we myst hack via fdisk #ROOTFS_PARTUUID=$(blkid |grep -e "$ROOTFS" |sed -n 's/^.*PARTUUID=/PARTUUID=/p') export LC_ALL=C ROOTFS_DISKID=$(fdisk -l "$DEVICE" | grep -e "Disk identifier" | sed -n 's/^.*Disk identifier: 0x/PARTUUID=/p') if [ $ROOTFS_DISKID = "" ]; then die "Failed to read DISKID" fi BOOTFS_PARTUUID="$ROOTFS_DISKID-01" ROOTFS_PARTUUID="$ROOTFS_DISKID-02" debug "PARTUUID for ROOTFS is $ROOTFS_PARTUUID" if [ $DEBUG -eq 1 ]; then parted -s $DEVICE print fi # # Check if any $DEVICE partitions are mounted after partitioning # unmount_device || die "Failed to unmount $DEVICE partitions" # # Format $DEVICE partitions # info "Formatting partitions" debug "Formatting $BOOTFS as ext2" mkfs.ext2 -F -F -L BOOT $BOOTFS 1>&3 2>&1 || die "Failed to format $BOOTFS" debug "Formatting $ROOTFS as ext4" mkfs.ext4 -F $ROOTFS -L "ROOT" 1>&3 2>&1 || die "Failed to format $ROOTFS" # Mounting image file system on loop devices # case $IMG_TYPE in MOUNT) debug "Mounting images and device in preparation for installation" mount -o loop $HDDIMG $HDDIMG_MNT 1>&3 2>&1 || die "Failed to mount $HDDIMG" mount -o loop $HDDIMG_MNT/rootfs.img $HDDIMG_ROOTFS_MNT 1>&3 2>&1 || die "Failed to mount rootfs.img" ;; DISK) debug "Attaching image and mounting partitions then device in preparation for installation" LOOP_DEVICE=`losetup --find` || die "Failled to find an available loop device see: losetup --find" losetup -P $LOOP_DEVICE $HDDIMG 1>&3 2>&1 || die "Attaching $LOOP_DEVICE from $HDDIMG failled" mount "$LOOP_DEVICE"p2 $HDDIMG_ROOTFS_MNT 1>&3 2>&1 || die "Failed to mount $LOOP_DEVICEp1 on $HDDIMG_ROOTFS_MNT" mount "$LOOP_DEVICE"p1 $HDDIMG_MNT 1>&3 2>&1 || die "Failed to mount $LOOP_DEVICEp2 on $HDDIMG_MNT" ;; *) die "unknown image format $IMG_TYPE" ;; esac mount $ROOTFS $ROOTFS_MNT 1>&3 2>&1 || die "Failed to mount $ROOTFS on $ROOTFS_MNT" mount $BOOTFS $BOOTFS_MNT 1>&3 2>&1 || die "Failed to mount $BOOTFS on $BOOTFS_MNT" info "Preparing boot partition" # create the config file for iasImage # Remove any existing root= kernel parameters and: # o Add a root= parameter with the target rootfs # o Specify ro so fsck can be run during boot # o Specify rootwait in case the target media is an asyncronous block device # such as MMC or USB disks # o Specify "quiet" to minimize boot time when using slow serial consoles # iasImage command line file creation echo "root=$ROOTFS_PARTUUID" > $IAS_CMD_LINE echo "console=$MRB_DEBUG_TTY" >> $IAS_CMD_LINE echo "earlycon=uart8250,mmio32,0xfc000000,115200n8" >> $IAS_CMD_LINE echo "rootwait" >> $IAS_CMD_LINE echo "video=$MRB_HDMI" >> $IAS_CMD_LINE echo "i915.enable_initial_modeset=1" >> $IAS_CMD_LINE debug "temp config for iasImage is $IAS_CMD_LINE" if [ -f $HDDIMG_MNT/vmlinuz ]; then KERNEL_TYPE="vmlinuz" debug "kernel is vmlinuz" fi if [ -f $HDDIMG_MNT/bzimage ]; then KERNEL_TYPE="bzimage" debug "kernel is bzimage -> vmlinuz" fi if [ -f $HDDIMG_MNT/microcode.cpio ]; then warn "initrd=microcode.cpio is not a supported configuration, microcode.cpio has been ignored" fi [ -z $KERNEL_TYPE ] && die "Linux kernel type in $HDDIMG is unsupported" if [ -f $HDDIMG_MNT/initrd ]; then info "creating ABL image with initramsfs" debug "$IAS_IMAGE_TOOL -o $BOOTFS_MNT/iasImage -i 0x30000 $IAS_CMD_LINE $HDDIMG_MNT/$KERNEL_TYPE $HDDIMG_MNT/initrd" $IAS_IMAGE_TOOL -o $BOOTFS_MNT/iasImage -i 0x30000 $IAS_CMD_LINE $HDDIMG_MNT/$KERNEL_TYPE $HDDIMG_MNT/initrd else info "creating ABL image without initramfs" debug "$IAS_IMAGE_TOOL -o $BOOTFS_MNT/iasImage -i 0x30000 $IAS_CMD_LINE $HDDIMG_MNT/$KERNEL_TYPE" $IAS_IMAGE_TOOL -o $BOOTFS_MNT/iasImage -i 0x30000 $IAS_CMD_LINE $HDDIMG_MNT/$KERNEL_TYPE fi printf "Copying ROOTFS files ... " command -v rsync >/dev/null 2>&1 # check if rsync exists if [ $DEBUG -eq 1 ] && [ $? -eq 0 ]; then rsync --info=progress2 -h -aHAXW --no-compress $HDDIMG_ROOTFS_MNT/* $ROOTFS_MNT 1>&3 2>&1 || die "Root FS copy failed" else cp -a $HDDIMG_ROOTFS_MNT/* $ROOTFS_MNT 1>&3 2>&1 || die "Root FS copy failed" fi debug "removing any swap entry in /etc/fstab" sed --in-place '/swap/d' $ROOTFS_MNT/etc/fstab debug "fixing PARTUUID for /boot" sed --in-place -e "s#PARTUUID=[0-9a-z-]\+\t/boot#${BOOTFS_PARTUUID}\t/boot#" $ROOTFS_MNT/etc/fstab printf "flushing data on removable device. May take a while ... " sync --file-system $ROOTFS_MNT echo done # We dont want udev to mount our root device while we're booting... if [ -d $ROOTFS_MNT/etc/udev/ ] ; then echo "$TARGET_DEVICE" >> $ROOTFS_MNT/etc/udev/mount.blacklist fi # Call cleanup to unmount devices and images and remove the TMPDIR cleanup echo "" if [ $WARNINGS -ne 0 ] && [ $ERRORS -eq 0 ]; then echo "${YELLOW}Installation completed with warnings${CLEAR}" echo "${YELLOW}Warnings: $WARNINGS${CLEAR}" elif [ $ERRORS -ne 0 ]; then echo "${RED}Installation encountered errors${CLEAR}" echo "${RED}Errors: $ERRORS${CLEAR}" echo "${YELLOW}Warnings: $WARNINGS${CLEAR}" else success "Installation completed successfully" fi echo ""