diff --git a/package/FORCE.icns b/package/FORCE.icns deleted file mode 100644 index 934a0d31..00000000 Binary files a/package/FORCE.icns and /dev/null differ diff --git a/package/README.md b/package/README.md index 9139ef91..4382c3ed 100644 --- a/package/README.md +++ b/package/README.md @@ -1,28 +1,66 @@ +# FORCE One-Step Installer Creation +FORCE one-step installers are created using the `cx_Freeze` python package after creating a python environment using either `venv` or `conda`. +A computer with the same operating system and architecture as the target operating system must be used to generate an installer, i.e. use a Windows machine to generate a Windows installer, a Mac (Intel) to generate a Mac installer, etc. +Note that installers generated with Apple computers with M-series chips will not be backwards compatible with Intel-based Apple computers. -Creating package way 1: +Windows and macOS are the only operating systems currently supported. +Linux users are encouraged to use pip-installed or source built versions of the RAVEN, HERON, and TEAL software packages. -python3.10 -m venv test310 -source test310/bin/activate -pip install cx_Freeze -pip install raven-framework teal-ravenframework heron-ravenframework +## 1. Build FORCE executables +Create a conda environment `force_build_310`, install the RAVEN, HERON, and TEAL pip packages, and build the FORCE executables using the script +```console +./build_force.sh +``` +## 2. Add IPOPT to build directory (Windows only) +Download the IPOPT Windows binary: +https://github.com/coin-or/Ipopt/releases -python setup.py install_exe --install-dir raven_install +Extract the downloaded zip directory and copy its contents to the raven_install directory, ensuring to replace the version numbers of IPOPT as needed. +```console +cd force_install +unzip ~/Downloads/Ipopt-3.14.12-win64-msvs2019-md.zip +mv Ipopt-3.14.12-win64-msvs2019-md local +cd .. +``` +## 3. Copy examples and build/copy the RAVEN, HERON, and TEAL documentation +Adding examples and documentation to the one-step installer requires having the source installation present on the build machine, with the `raven_libraries` conda environment already created. +```console +conda activate raven_libraries +./copy_examples.sh --raven-dir /path/to/raven --heron-dir /path/to/HERON +cp -R examples force_install/examples +./make_docs.sh --raven-dir /path/to/raven --heron-dir /path/to/HERON --teal-dir /path/to/TEAL +cp -R docs force_install/docs +``` +When running the `make_docs.sh` script, the optional `--no-build` flag may be added if the desired documentation PDFs have already been built, and you do not wish to rebuild the documents. -Way 2: +## 4. Get NEAMS Workbench installer +The installers for the NEAMS Workbench software can be found here: +https://code.ornl.gov/neams-workbench/downloads/-/tree/5.4.1?ref_type=heads -conda create -n test39 python=3.9 -conda activate test39 +Download `Workbench-5.4.1.exe` for Windows and `Workbench-5.4.1.dmg` for macOS. +Place this file in the current directory. -pip install cx_Freeze -pip install raven-framework teal-ravenframework heron-ravenframework +Windows: +```console +cp ~/Downloads/Workbench-5.4.1.exe . +``` -python setup.py install_exe --install-dir raven_install +macOS: +```console +cp ~/Downloads/Workbench-5.4.1.dmg . +``` -ipopt work: - -cd raven_install -unzip ~/Downloads/Ipopt-3.14.12-win64-msvs2019-md.zip -mv Ipopt-3.14.12-win64-msvs2019-md local +## 5. Create the installer +### Windows +The Windows installer is created using Inno Setup. +Run the `inno_package.iss` script from the Inno Setup application. +The resulting .exe installer file can be found in the `inno_output` directory. +### macOS +Run the macOS build script +```console +./build_mac_app.sh +``` +The disk image `FORCE.dmg` contains applications for both FORCE and Workbench. diff --git a/package/build_force b/package/build_force deleted file mode 100755 index d37806a1..00000000 --- a/package/build_force +++ /dev/null @@ -1,141 +0,0 @@ -#!/bin/bash - -# Parse command line arguments: -# --conda-defs : (Optional) Path to the conda.sh file -# --env-file : (Optional) Path to a conda environment YAML file. default: environment.yml -# --raven-loc : (Optional) Path to the RAVEN source code directory -# --heron-loc : (Optional) Path to the HERON source code directory. default: $RAVEN_LOC/plugins/HERON -# --teal-loc : (Optional) Path to the TEAL source code directory. default: $RAVEN_LOC/plugins/TEAL - -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) - -while test $# -gt 0; do - case "$1" in - --conda-defs) - shift - CONDA_DEFS=$1 - shift - ;; - --env-file) - shift - ENV_FILE=$1 - shift - ;; - --raven-loc) - shift - RAVEN_LOC=$1 - shift - ;; - --heron-loc) - shift - HERON_LOC=$1 - shift - ;; - --teal-loc) - shift - TEAL_LOC=$1 - shift - ;; - *) - break - ;; - esac -done - -# Function to find RAVEN given a list of possible locations -function find_raven { - for loc in ${LOCATIONS[@]}; do - for dir in $(find $loc -maxdepth 1 -type d -iname "RAVEN"); do - echo $(realpath $dir) - return - done - done - echo "Directory $1 not found in any of the locations: ${LOCATIONS[@]}" - exit 1 -} - -# Function to find RAVEN plugins given a list of possible locations -function find_plugin { - for loc in ${LOCATIONS[@]} $RAVEN_LOC; do - for dir in $(find $loc -maxdepth 3 -type d -iname $1 2> /dev/null); do - echo $(realpath $dir) - return - done - done - echo "Directory $1 not found in any of the locations: ${LOCATIONS[@]}/plugins" - exit 1 -} - -# If the RAVEN, HERON, and TEAL locations have not been given as arguments, look for them in a few -# places before giving up. -LOCATIONS=($SCRIPT_DIR/../.. $HOME) -if [ -z "$RAVEN_LOC" ]; then - RAVEN_LOC=$(find_raven) -fi -if [ -z "$HERON_LOC" ]; then - HERON_LOC=$(find_plugin HERON) -fi -if [ -z "$TEAL_LOC" ]; then - TEAL_LOC=$(find_plugin TEAL) -fi - -echo "Software locations:" -echo " ... RAVEN: $RAVEN_LOC" -echo " ... HERON: $HERON_LOC" -echo " ... TEAL: $TEAL_LOC" - -# Configure conda to run in the current shell -# If CONDA_DEFS is defined, use it to set the conda environment. Otherwise, we'll assume that the -# environment has already been activated. -if [ -z "$CONDA_DEFS" ]; then - echo "Using currently active python environment." - echo " ... Python location: $(which python)" -else - source $CONDA_DEFS - - if [ -z "$ENV_FILE" ]; then - ENV_FILE=$SCRIPT_DIR/environment.yml - fi - - # Get the name of the conda environment in the environment.yml file - echo "Reading the environment file $ENV_FILE" - ENV_NAME=$(grep name $ENV_FILE | head -n 1 | cut -d ' ' -f 2) - echo " ... Found environment name: $ENV_NAME" - - # If the environment already exists, update it based on the file contents. Otherwise, create it. - if conda info --envs | grep -q force; then - echo " ... Updating the existing environment $ENV_NAME" - conda env update -f $ENV_FILE --name $ENV_NAME - else - echo " ... Creating a new environment $ENV_NAME" - conda env create -f environment.yml - fi - - # Activate the environment - conda activate $ENV_NAME -fi - -# Build the FORCE executables -echo "Building the FORCE executables" -python setup.py install_exe --install-dir $SCRIPT_DIR/force_install - -# Build the documentation for the FORCE package tools and add them to the install directory -echo "Building the FORCE package tools documentation" -echo " ... RAVEN location: $RAVEN_LOC" -echo " ... HERON location: $HERON_LOC" -echo " ... TEAL location: $TEAL_LOC" -echo " ... Destination: $SCRIPT_DIR/force_install/docs" -# The --no-build flag is used to avoid building the documentation. This is because the documentation -# requires activatation of the raven_libraries conda environment, otherwise the HERON documentation -# build will fail. Rebuild the documentation before building the FORCE package! -sh $SCRIPT_DIR/make_docs --raven-dir $RAVEN_LOC --heron-dir $HERON_LOC --teal-dir $TEAL_LOC --dest $SCRIPT_DIR/force_install/docs --no-build - -# Copy over relevant examples -echo "Copying over the FORCE examples" -echo " ... Destination: $SCRIPT_DIR/force_install/examples" -sh $SCRIPT_DIR/copy_examples --raven-dir $RAVEN_LOC --heron-dir $HERON_LOC --dest $SCRIPT_DIR/force_install/examples - -# Copy over Workbench installer .exe. This is only for the Windows standalone install. -if [[ "$OSTYPE" == "msys" ]]; then - cp $SCRIPT_DIR/Workbench-5.4.1.exe $SCRIPT_DIR/force_install/ -fi diff --git a/package/build_force.sh b/package/build_force.sh new file mode 100755 index 00000000..a1b505fb --- /dev/null +++ b/package/build_force.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +# Have users point to the location of their conda installation so we can properly activate the +# conda environment that is being made. Use the "--conda-defs" option to specify this path. +while [[ $# -gt 0 ]]; do + key="$1" + case $key in + --conda-defs) + CONDA_DEFS="$2" + shift + shift + ;; + *) + echo "Unknown option: $1" + exit 1 + ;; + esac +done + +# Establish conda environment +conda create -n force_build_310 python=3.10 -y +source $CONDA_DEFS +conda activate force_build_310 + +# Check that the conda environment is active. If not, exit. +if [[ $CONDA_DEFAULT_ENV != "force_build_310" ]]; then + echo "Conda environment not activated. Maybe the path to the conda installation is incorrect?" + echo "Provided conda path: $CONDA_DEFS" + exit 1 +fi + +pip install cx_Freeze +pip install raven-framework heron-ravenframework teal-ravenframework +# If on macOS, use conda to install ipopt +if [[ "$OSTYPE" == "darwin"* ]]; then + # Note: The PyPI version of ipopt is not maintained and is severl major version + # behind the conda-forge distribution. + conda install -c conda-forge ipopt -y +fi + +# Build the FORCE executables +python setup.py install_exe --install-dir force_install diff --git a/package/build_force_mac.sh b/package/build_mac_app.sh similarity index 71% rename from package/build_force_mac.sh rename to package/build_mac_app.sh index f4e0a3cd..53b4f553 100755 --- a/package/build_force_mac.sh +++ b/package/build_mac_app.sh @@ -1,5 +1,4 @@ -# Freeze the FORCE app using cx_Freeze. We require a suitable python environment to be active. -sh build_force +#!/bin/bash # Set up the FORCE application bundle # We'll set up the app so that some FORCE launcher script is the main executable, and the RAVEN, @@ -7,9 +6,9 @@ sh build_force # Build the initial app from the force_launcher.scpt AppleScript osacompile -o FORCE.app force_launcher.scpt # Now copy over the force_install directory contents to the application's Resources directory -cp -R force_install/* FORCE.app/Contents/Resources/ +cp -Rp force_install/* FORCE.app/Contents/Resources/ # Overwrite the app's icon with the FORCE icon -cp FORCE.icns FORCE.app/Contents/Resources/applet.icns +cp icons/FORCE.icns FORCE.app/Contents/Resources/applet.icns # Create a new disk image hdiutil create -size 5g -fs HFS+ -volname "FORCE" -o force_build.dmg @@ -20,9 +19,20 @@ hdiutil attach force_build.dmg -mountpoint /Volumes/FORCE # Mount the existing .dmg file file Workbench hdiutil attach Workbench-5.4.1.dmg -mountpoint /Volumes/Workbench +# Add the workshop tests and data directories to FORCE so that Workbench's autocomplete works for workshop examples +mkdir FORCE.app/Contents/Resources/tests +mkdir FORCE.app/Contents/Resources/examples +cp -Rp examples/workshop FORCE.app/Contents/Resources/tests/ +cp -Rp examples/data FORCE.app/Contents/Resources/examples/ + # Move the FORCE app to the disk image -cp -R FORCE.app /Volumes/FORCE/ -cp -R /Volumes/Workbench/Workbench-5.4.1.app /Volumes/FORCE/ +cp -Rp FORCE.app /Volumes/FORCE/ +cp -Rp /Volumes/Workbench/Workbench-5.4.1.app /Volumes/FORCE/ + +# Move the "examples" and "docs" directories from the FORCE app bundle to the top level of the disk +# image to make them more accessible. +cp -Rp examples /Volumes/FORCE/ +cp -Rp docs /Volumes/FORCE/ # Move the "examples" and "docs" directories from the FORCE app bundle to the top level of the disk # image to make them more accessible. diff --git a/package/copy_examples b/package/copy_examples deleted file mode 100755 index 7a7572e8..00000000 --- a/package/copy_examples +++ /dev/null @@ -1,108 +0,0 @@ -#!/bin/bash -# Copies examples from the test directories of RAVEN and HERON to provide examples for users using -# the standalone install version of FORCE. - -# Get the RAVEN and HERON locations as arguments "--raven-dir" and "--heron-dir" -# The destination directory is "examples" in the current directory but may be changed with the -# "--dest" argument. -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -EXAMPLES_DIR="$SCRIPT_DIR/examples" - -while [[ $# -gt 0 ]] -do - key="$1" - case $key in - --raven-dir) - RAVEN_DIR="$2" - shift - shift - ;; - --heron-dir) - HERON_DIR="$2" - shift - shift - ;; - --dest) - EXAMPLES_DIR="$2" - shift - shift - ;; - *) - echo "Unknown option: $1" - exit 1 - ;; - esac -done - -# The examples we want to copy are the RAVEN user_guide tests, the HERON workshop tests, and the -# HERON data directory which contains time series models for those workshop tests. -EXAMPLES=($RAVEN_DIR/tests/framework/user_guide $HERON_DIR/data $HERON_DIR/tests/workshop) -mkdir -p $EXAMPLES_DIR - -for ex in ${EXAMPLES[@]}; do - cp -R "$ex" "$EXAMPLES_DIR" -done - -# Clean up the copied examples, removing files and directories created when running the tests. -FILES_TO_REMOVE=("tests" "moped_input.xml" "outer.xml" "inner.xml" "cash.xml" "*.lib") -DIRS_TO_REMOVE=("__pycache__" "gold" "*_o") -for filename in ${FILES_TO_REMOVE[@]}; do - find $EXAMPLES_DIR -name $filename -exec rm {} \; 2>/dev/null -done -for dirname in ${DIRS_TO_REMOVE[@]}; do - find $EXAMPLES_DIR -type d -name $dirname -exec rm -r {} \; 2>/dev/null -done - -# If building on Mac, replace the %HERON_DATA% magic string with a relative path to the data -# directory. This is a little hacky but the %HERON_DATA% magic string doesn't look everywhere for -# the data directory. This is only an issue for the Mac standalone install. HERON will find the -# data directory correctly on Windows. -DATA_DIR=$EXAMPLES_DIR/data -if [[ "$OSTYPE" == "darwin"* ]]; then - # Find all XML files recursively from the current directory - find $EXAMPLES_DIR/workshop -type f -name "*.xml" | while read -r file; do - # Check if the file contains the %HERON_DATA% magic string. If not, skip this file. - grep -q "%HERON_DATA%" "$file" || continue - - # Get the directory of the current XML file - FILE_DIR=$(dirname "$file") - - # Calculate the relative path from the XML file directory to the data directory - echo "FILE_DIR: $FILE_DIR DATA_DIR: $DATA_DIR" - RELATIVE_PATH=$(python -c "import os.path; print(os.path.relpath('$DATA_DIR', '$FILE_DIR'))") - # RELATIVE_PATH=$(realpath -s --relative-to="$FILE_DIR" "$DATA_DIR") - echo $RELATIVE_PATH - - # Use sed to replace %HERON_DATA% with the relative path to the data directory - sed -i '' "s|%HERON_DATA%|$RELATIVE_PATH|g" "$file" - done -fi - -# If Workbench is installed, convert HERON workshop tests to .heron files -if [[ "$OSTYPE" == "darwin"* ]]; then - # Find the Workbench-*.app directory in the Applications directory - WORKBENCH_APP=$(find /Applications -type d -name "Workbench-*.app" | head -n 1) - XML2EDDI=$(realpath $WORKBENCH_APP/Contents/rte/util/xml2eddi.py) -else - if ! command -v Workbench &> /dev/null; then - # If Workbench isn't in the system PATH. Look in typical installation locations for Workbench to - # find the xml2eddi.py script. Common locatoins are /c/Workbench-5.4.1/ and $HOME/Workbench-5.4.1/. - if [ -d "/c/Workbench-5.4.1" ]; then - XML2EDDI="/c/Workbench-5.4.1/rte/util/xml2eddi.py" - elif [ -d "$HOME/Workbench-5.4.1" ]; then - XML2EDDI="$HOME/Workbench-5.4.1/rte/util/xml2eddi.py" - else - XML2EDDI="" # Workbench not found - fi - else - # Workbench is in the system PATH. Use the location of the Workbench executable to find the xml2eddi.py script. - XML2EDDI=$(realpath $(command -v Workbench)/../rte/util/xml2eddi.py) - fi -fi - -# If XML2EDDI is not empty, convert the HERON workshop tests to .heron files -if [ -n "$XML2EDDI" ]; then - for ex in $(find $EXAMPLES_DIR/workshop -name "heron_input*.xml"); do - python $XML2EDDI $ex > ${ex%.xml}.heron - done -fi diff --git a/package/copy_examples.sh b/package/copy_examples.sh new file mode 100755 index 00000000..6bf2ca06 --- /dev/null +++ b/package/copy_examples.sh @@ -0,0 +1,79 @@ +#!/bin/bash +# Copies examples from the test directories of RAVEN and HERON to provide examples for users using +# the standalone install version of FORCE. + +# Get the RAVEN and HERON locations as arguments "--raven-dir" and "--heron-dir" +# The destination directory is "examples" in the current directory but may be changed with the +# "--dest" argument. +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +EXAMPLES_DIR="$SCRIPT_DIR/examples" + +while [[ $# -gt 0 ]] +do + key="$1" + case $key in + --raven-dir) + RAVEN_DIR="$2" + shift + shift + ;; + --heron-dir) + HERON_DIR="$2" + shift + shift + ;; + --dest) + EXAMPLES_DIR="$2" + shift + shift + ;; + *) + echo "Unknown option: $1" + exit 1 + ;; + esac +done + +# The examples we want to copy are the RAVEN user_guide tests, the HERON workshop tests, and the +# HERON data directory which contains time series models for those workshop tests. +EXAMPLES=($RAVEN_DIR/tests/framework/user_guide $HERON_DIR/data $HERON_DIR/tests/workshop) +mkdir -p $EXAMPLES_DIR + +for ex in ${EXAMPLES[@]}; do + cp -R "$ex" "$EXAMPLES_DIR" +done + +# Clean up the copied examples, removing files and directories created when running the tests. +DIRS_TO_REMOVE=("__pycache__" "gold" "*_o") +for dirname in ${DIRS_TO_REMOVE[@]}; do + find $EXAMPLES_DIR -type d -name $dirname -exec rm -r {} \; 2>/dev/null +done +FILES_TO_REMOVE=("tests" "moped_input.xml" "outer.xml" "inner.xml" "cash.xml" "*.lib" "write_inner.py" "*.heron" "*.heron.xml") +for filename in ${FILES_TO_REMOVE[@]}; do + find $EXAMPLES_DIR -name $filename -exec rm {} \; 2>/dev/null +done + +# If building on Mac, replace the %HERON_DATA% magic string with a relative path to the data +# directory. This is a little hacky but the %HERON_DATA% magic string doesn't look everywhere for +# the data directory. This is only an issue for the Mac standalone install. HERON will find the +# data directory correctly on Windows. +# DATA_DIR=$EXAMPLES_DIR/data +# if [[ "$OSTYPE" == "darwin"* ]]; then +# # Find all XML files recursively from the current directory +# find $EXAMPLES_DIR/workshop -type f -name "*.xml" | while read -r file; do +# # Check if the file contains the %HERON_DATA% magic string. If not, skip this file. +# grep -q "%HERON_DATA%" "$file" || continue + +# # Get the directory of the current XML file +# FILE_DIR=$(dirname "$file") + +# # Calculate the relative path from the XML file directory to the data directory +# echo "FILE_DIR: $FILE_DIR DATA_DIR: $DATA_DIR" +# RELATIVE_PATH=$(python -c "import os.path; print(os.path.relpath('$DATA_DIR', '$FILE_DIR'))") +# # RELATIVE_PATH=$(realpath -s --relative-to="$FILE_DIR" "$DATA_DIR") +# echo $RELATIVE_PATH + +# # Use sed to replace %HERON_DATA% with the relative path to the data directory +# sed -i '' "s|%HERON_DATA%|$RELATIVE_PATH|g" "$file" +# done +# fi diff --git a/package/default.apps.son b/package/default.apps.son new file mode 100644 index 00000000..68e86634 --- /dev/null +++ b/package/default.apps.son @@ -0,0 +1,13 @@ +applications { + HERON { + configurations { + default { + options { + shared { + Executable="/Applications/FORCE.app/Contents/Resources/heron" + } + } + } + } + } +} diff --git a/package/environment.yml b/package/environment.yml deleted file mode 100644 index 77190711..00000000 --- a/package/environment.yml +++ /dev/null @@ -1,9 +0,0 @@ -name: force_build_310 -dependencies: -- python=3.10 -- pip -- pip: - - cx_Freeze - - raven-framework - - heron-ravenframework - - teal-ravenframework diff --git a/package/heron_64.ico b/package/heron_64.ico deleted file mode 100644 index 08442ff4..00000000 Binary files a/package/heron_64.ico and /dev/null differ diff --git a/package/make_docs b/package/make_docs.sh similarity index 97% rename from package/make_docs rename to package/make_docs.sh index b4a6d336..dc43c52c 100755 --- a/package/make_docs +++ b/package/make_docs.sh @@ -74,8 +74,7 @@ echo "FORCE documentation directory: $DOC_DIR" mkdir -p "$DOC_DIR" # Build the documentation for the FORCE tools -# for loc in RAVEN_DIR HERON_DIR TEAL_DIR; do -for loc in HERON_DIR TEAL_DIR; do +for loc in RAVEN_DIR HERON_DIR TEAL_DIR; do pushd "${!loc}/doc" > /dev/null echo $(pwd) diff --git a/package/raven_64.ico b/package/raven_64.ico deleted file mode 100644 index e09427c8..00000000 Binary files a/package/raven_64.ico and /dev/null differ diff --git a/package/setup.py b/package/setup.py index ba111e93..1f0075bf 100644 --- a/package/setup.py +++ b/package/setup.py @@ -41,8 +41,8 @@ name="force", version="0.1", description="FORCE package", - executables=[Executable(script="raven_framework.py",icon="raven_64.ico"), - Executable(script="heron.py",icon="heron_64.ico"), - Executable(script="teal.py",icon="teal_64.ico")], + executables=[Executable(script="raven_framework.py",icon="icons/raven_64.ico"), + Executable(script="heron.py",icon="icons/heron_64.ico"), + Executable(script="teal.py",icon="icons/teal_64.ico")], options={"build_exe": build_exe_options}, ) diff --git a/package/teal_64.ico b/package/teal_64.ico deleted file mode 100644 index a55d7221..00000000 Binary files a/package/teal_64.ico and /dev/null differ