Revision control

Copy as Markdown

#!/bin/bash
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# Nimbus Feature Manifest Language Generator
#
# For more information, check out https://experimenter.info/fml-spec
#
# This script generates Swift definitions for all the experimentable features supported by Nimbus.
# It generates Swift code to be included in the final build.
#
# To use it in a Swift project, follow these steps:
# 1. Import the `nimbus-fml.sh` script into your project.
# 2. Edit the `nimbus-fml-configuration.sh` file to suit your project.
# 3. Add a `<NAME>.fml.yaml` feature manifest file. Check out https://experimenter.info/fml-spec for the spec.
# 4. Test the new file by running this file from SOURCE_ROOT.
# 5. Add a new "Run Script" build step and set the command to `bash $PWD/nimbus-fml.sh`.
# 6. Run the build.
# 7. Add the "FML.swift" file in the `Generated` folder to your project.
# 8. Add the same "FML.swift" from the `Generated` folder as Output Files of the newly created "Run Script" step.
# 9. Start using the generated feature code.
set -euo pipefail
DIRNAME=$(dirname "$0")
# CMDNAME is used in the usage text below.
# shellcheck disable=SC2034
CMDNAME=$(basename "$0")
USAGE=$(cat <<HEREDOC
${CMDNAME}
Nimbus Feature Manifest Language generator initializer.
This script generates the code needed to interact with Nimbus, exposing features which are experimentable.
For more information, check out https://experimenter.info/fml-spec
The script structure was adopted from the similar script "sdk_generator.sh" written by the Glean team.
This script should be executed as a "Run Build Script" phase from Xcode, but you can run it from the command line
if you define "PROJECT", "CONFIGURATION" and "SOURCE_ROOT" environment variables.
Application-specific configuration options for this script has moved to ./nimbus-fml-configuration.sh alongside this file.
Local developer specific configuration should go in ./nimbus-fml-configuration.local.sh and not checked into to source control.
USAGE:
${CMDNAME} [OPTIONS] [FILE]
OPTIONS:
-a, --use-fml-version <REF> Version or reference of nimbus-fml to use. If missing, derives from the project.pbxproj file.
-F, --fresh Re-download the nimbus-fml binary.
-h, --help display this help message.
HEREDOC
)
helptext() {
echo "$USAGE"
}
# fail_trap is executed if an error occurs.
fail_trap() {
local result=$1
local line_number=$2
echo "Error calling nimbus-fml.sh at line ${line_number}"
exit "$result"
}
#Stop execution on any error
trap 'fail_trap $? $LINENO' ERR
if [ -z "${SOURCE_ROOT:-}" ] ; then
echo "Warning: No \$SOURCE_ROOT defined."
echo " Execute this script as a build step in Xcode."
echo " Guessing it as CWD"
SOURCE_ROOT="$(pwd)"
fi
if [ -z "${PROJECT:-}" ]; then
echo "Warning: No \$PROJECT defined."
echo " Execute this script as a build step in Xcode."
xcodeproj=$(ls -d "${SOURCE_ROOT}"/*.xcodeproj || exit 2 | head -n 1 )
PROJECT=$(basename -s .xcodeproj "$xcodeproj")
echo " Detected it as $PROJECT"
fi
if [ -z "${CONFIGURATION:-}" ] ; then
echo "Warning: No \$CONFIGURATION defined."
echo " Execute this script as a build step in Xcode."
echo " Guessing it as Debug"
CONFIGURATION=Debug
fi
find_as_version() {
# We can derive the version we need by looking at the project file.
number_string=$(grep -A 3 $'https://github.com/mozilla/rust-components-swift' "$SOURCE_ROOT/$PROJECT.xcodeproj/project.pbxproj" | grep -E -o "\d+\.\d+\.\d+")
if [ -z "$number_string" ]; then
# If there is no rust-components then perhaps we're building with a local versions of rust_components, using rust_components_local.sh .
# We try to resolve that, and find the version from the Package.swift file in that local directory.
rust_components_path=$(grep -A 3 $'XCRemoteSwiftPackageReference "rust-components-swift"' "$SOURCE_ROOT/$PROJECT.xcodeproj/project.pbxproj" | grep 'repositoryURL = "file://' | grep -o -E '/\w[^"]+')
number_string=$(grep 'let version =' "$rust_components_path/Package.swift" | grep -E -o "\d+\.\d+\.\d+")
fi
if [ -z "$number_string" ]; then
echo "Error: No https://github.com/mozilla/rust-components-swift package was detected."
echo "The package must be added as a project dependency."
exit 2
fi
## We try and differentiate between two version types.
## Nightlies: 121.0.20231027173509 --> 121.20231027173509
## Releases: 120.0.0 --> 120.0
## 120.0.1 --> 120.0.1
## 120.1.0 --> 120.1.0
# Get the last component, i.e. the patch number…
local patch=${number_string##*.}
# … and check if it's more that 2 digits in length
if [[ ${#patch} -gt 2 ]] ; then
# If so, then it's likely a date stamp, so a nightly.
# We have a middle 0 which we need to get rid of,
# because taskcluster's versions are MAJOR.DATETIME
AS_VERSION=${number_string//\.0\./\.}
else
# Otherwise, it's a release!
# These versions have rules about zero
# minor number and patch numbers, so we
# rationalize that.
AS_VERSION=${number_string/\.0\.0/.0}
fi
}
FRESHEN_FML=
AS_VERSION=
MOZ_APPSERVICES_LOCAL=
REPO_FILES=
NIMBUS_DIR="$SOURCE_ROOT/build/nimbus"
export CACHE_DIR="$NIMBUS_DIR/fml-cache"
export APP_FML_FILE="$PROJECT/nimbus.fml.yaml"
export GENERATED_SRC_DIR=
export MOZ_APPSERVICES_MODULE=MozillaAppServices
export MODULES=$PROJECT
echo "Using $DIRNAME/nimbus-fml-configuration.sh as config"
# shellcheck disable=SC1091
source "$DIRNAME/nimbus-fml-configuration.sh"
local_config="$DIRNAME/nimbus-fml-configuration.local.sh"
if [ -f "$local_config" ] ; then
echo "Modifying with $local_config"
# shellcheck disable=SC1090
source "$local_config"
fi
while (( "$#" )); do
case "$1" in
-o|--output)
GENERATED_SRC_DIR=$2
shift 2
;;
-h|--help)
helptext
exit 0
;;
-a|--use-fml-version)
AS_VERSION=$2
shift 2
;;
-F|--fresh)
FRESHEN_FML="true"
shift 2
;;
--verbose)
set -x
shift 1
;;
--) # end argument parsing
shift
break
;;
--*=|-*) # unsupported flags
echo "Error: Unsupported flag $1" >&2
exit 1
;;
*) # preserve positional arguments
APP_FML_FILE=$1
shift
;;
esac
done
if [ -z "$APP_FML_FILE" ]; then
if [ -z "$SCRIPT_INPUT_FILE_COUNT" ] || [ "$SCRIPT_INPUT_FILE_COUNT" -eq 0 ]; then
echo "Error: No input files provided for the Nimbus Feature Manifest."
exit 2
fi
APP_FML_FILE=$SCRIPT_INPUT_FILE_0
fi
if [ -n "$MOZ_APPSERVICES_LOCAL" ] ; then
# If we've specified where app-services lives, perhaps we can run from a local copy.
LOCAL_FML_DIR="$MOZ_APPSERVICES_LOCAL/components/support/nimbus-fml"
export BINARY_PATH="$HOME/.cargo/bin/cargo run --manifest-path $LOCAL_FML_DIR/Cargo.toml --"
else
if [ -z "$AS_VERSION" ] ; then
find_as_version
fi
# Otherwise, we should download a pre-built copy
CHECKSUM_URL="$AS_DOWNLOAD_URL/nimbus-fml.sha256"
FML_URL="$AS_DOWNLOAD_URL/nimbus-fml.zip"
RELEASE_STATUS_CODE=$(curl -L --write-out '%{http_code}' --silent --output /dev/null "$CHECKSUM_URL" )
if [ "$RELEASE_STATUS_CODE" != "200" ]; then
CHECKSUM_URL="${AS_DOWNLOAD_URL}nimbus-fml.sha256"
FML_URL="${AS_DOWNLOAD_URL}nimbus-fml.zip"
fi
FML_DIR="$NIMBUS_DIR/$AS_VERSION/bin"
export BINARY_PATH="$FML_DIR/nimbus-fml"
# Check whether the cached copy is still the right one to use.
if [[ -f $FML_DIR/nimbus-fml.sha256 ]]; then
echo "Checking if we need to redownload the FML"
NEW_CHECKSUM=$(curl -L "$CHECKSUM_URL")
OLD_CHECKSUM=$(cat "$FML_DIR/nimbus-fml.sha256")
if [ ! "$OLD_CHECKSUM" == "$NEW_CHECKSUM" ]; then
FRESHEN_FML="true"
echo "The checksums don't match, redownloading the new FML"
fi
fi
if [ -n "$FRESHEN_FML" ]; then
rm -Rf "$FML_DIR"
fi
mkdir -p "$FML_DIR"
if [[ ! -f "$FML_DIR/nimbus-fml.zip" ]] ; then
echo "Downloading to $FML_DIR"
# We now download the nimbus-fml from the github release
curl -L "$FML_URL" --output "$FML_DIR/nimbus-fml.zip"
# We also download the checksum
curl -L "$CHECKSUM_URL" --output "$FML_DIR/nimbus-fml.sha256"
pushd "${FML_DIR}" || exit 1
shasum --check nimbus-fml.sha256
popd
fi
## We definitely have a zip file on disk, but we might already have done the work of unzipping it.
if [[ ! -f "$BINARY_PATH" ]] ; then
## So we've looked and there's no executable file of that name
## Now work out what arch version of the executable we want.
ARCH=$(uname -m)
if [[ "$ARCH" == 'x86_64' ]]
then
EXE_ARCH=x86_64-apple-darwin
elif [[ "$ARCH" == 'arm64' ]]
then
EXE_ARCH=aarch64-apple-darwin
else
echo "Error: Unsupported architecture. This script can only run on Mac devices running x86_64 or arm64"
exit 2
fi
# -o overwrites a file (if the file existed but isn't executable)
# -j junks the path, so we don't have to have $EXE_ARCH/release/ in our directory
# -d outputs to the directory of our choice.
unzip -o -j "$FML_DIR/nimbus-fml.zip" $EXE_ARCH/release/nimbus-fml -d "$FML_DIR"
fi
fi
echo_eval() {
local CMD="$*"
# Truncating the absolute paths into something easier to read.
local display=${CMD//"$SOURCE_ROOT"/\$SOURCE_ROOT}
echo "$display"
eval "$CMD"
}
echo "SOURCE_ROOT=$SOURCE_ROOT"
pushd "${SOURCE_ROOT}" > /dev/null || true
# We're going to assemble the repo args from the repo files that
repo_args=
for repo_file in $REPO_FILES ; do
repo_args="$repo_args --repo-file $repo_file"
done
# Now validate the FML file. This will load the YAML and print warnings or errors for each channel.
echo_eval "$BINARY_PATH validate $repo_args --cache-dir $CACHE_DIR $APP_FML_FILE"
# We'll generate the command, and output some nice copy/pastable version of the command to the Build console…
for module in $MODULES ; do
output_dir=""
input_pattern="$module"
output_dir=${GENERATED_SRC_DIR:-"${module}/Generated"}
# If $module is a file, then set the output directory to $PROJECT/Generated
# otherwise,
if [ -f "$module" ] ; then
output_dir="$PROJECT/Generated"
fi
mkdir -p "$output_dir"
echo_eval "$BINARY_PATH generate $repo_args --channel $CHANNEL --language swift --cache-dir $CACHE_DIR $input_pattern $output_dir"
done
popd > /dev/null || exit 0