1#!/usr/bin/env bash
2
3set -u
4set -e
5
6ROOT_PATH="$(cd "$(dirname $0)"; echo $PWD)"
7export ROOT_PATH
8OUTPUT_DIRECTORY="$ROOT_PATH/output"
9EXPORT_DIRECTORY=""
10
11UPDATE_SUBMODULES=false
12CONFIGURATION="Release"
13BUILD_PLATFORM="Any CPU"
14CLEAN=false
15PACKAGES=false
16NIGHTLY=false
17PORTABLE=false
18HEADLESS=false
19SKIP_FETCH=false
20TLIB_ONLY=false
21TLIB_EXPORT_COMPILE_COMMANDS=false
22TLIB_ARCH=""
23NET=false
24TFM="net462"
25GENERATE_DOTNET_BUILD_TARGET=true
26PARAMS=()
27CUSTOM_PROP=
28NET_FRAMEWORK_VER=
29RID="linux-x64"
30HOST_ARCH="i386"
31# Common cmake flags
32CMAKE_COMMON=""
33
34function print_help() {
35  echo "Usage: $0 [-cdvspnt] [-b properties-file.csproj] [--no-gui] [--skip-fetch] [--profile-build] [--tlib-only] [--tlib-export-compile-commands] [--tlib-arch <arch>] [--host-arch i386|aarch64] [-- <ARGS>]"
36  echo
37  echo "-c                                clean instead of building"
38  echo "-d                                build Debug configuration"
39  echo "-v                                verbose output"
40  echo "-p                                create packages after building"
41  echo "-n                                create nightly packages after building"
42  echo "-t                                create a portable package (experimental, Linux only)"
43  echo "-s                                update submodules"
44  echo "-b                                custom build properties file"
45  echo "-o                                custom output directory"
46  echo "--skip-fetch                      skip fetching submodules and additional resources"
47  echo "--no-gui                          build with GUI disabled"
48  echo "--force-net-framework-version     build against different version of .NET Framework than specified in the solution"
49  echo "--net                             build with dotnet"
50  echo "-B                                bundle target runtime (default value: $RID, requires --net, -t)"
51  echo "-F                                select the target framework for which Renode should be built (default value: $TFM)"
52  echo "--profile-build                   build optimized for profiling"
53  echo "--tlib-only                       only build tlib"
54  echo "--tlib-arch                       build only single arch (implies --tlib-only)"
55  echo "--tlib-export-compile-commands    build tlibs with 'compile_commands.json' (requires --tlib-arch)"
56  echo "--host-arch                       build with a specific tcg host architecture (default: i386)"
57  echo "--skip-dotnet-target-generation   don't generate 'Directory.Build.targets' file, useful when experimenting with different build settings"
58  echo "<ARGS>                            arguments to pass to the build system"
59}
60
61while getopts "cdvpnstb:o:B:F:-:" opt
62do
63  case $opt in
64    c)
65      CLEAN=true
66      ;;
67    d)
68      CONFIGURATION="Debug"
69      ;;
70    v)
71      PARAMS+=(verbosity:detailed)
72      ;;
73    p)
74      PACKAGES=true
75      ;;
76    n)
77      NIGHTLY=true
78      PACKAGES=true
79      ;;
80    t)
81      PORTABLE=true
82      ;;
83    s)
84      UPDATE_SUBMODULES=true
85      ;;
86    b)
87      CUSTOM_PROP=$OPTARG
88      ;;
89    o)
90      EXPORT_DIRECTORY=$OPTARG
91      echo "Setting the output directory to $EXPORT_DIRECTORY"
92      ;;
93    B)
94      RID=$OPTARG
95      ;;
96    F)
97      if ! $NET; then
98        echo "-F requires --net being set"
99        exit 1
100      fi
101      TFM=$OPTARG
102      ;;
103    -)
104      case $OPTARG in
105        "no-gui")
106          HEADLESS=true
107          ;;
108        "skip-fetch")
109          SKIP_FETCH=true
110          ;;
111        "force-net-framework-version")
112          shift $((OPTIND-1))
113          NET_FRAMEWORK_VER="p:TargetFrameworkVersion=v$1"
114          PARAMS+=("$NET_FRAMEWORK_VER")
115          OPTIND=2
116          ;;
117        "net")
118          NET=true
119          TFM="net8.0"
120          PARAMS+=(p:NET=true)
121          ;;
122        "profile-build")
123          CMAKE_COMMON="-DPROFILING_BUILD=ON"
124          ;;
125        "tlib-only")
126          TLIB_ONLY=true
127          ;;
128        "tlib-arch")
129          # This only makes sense with '--tlib-only' set; it might as well imply it
130          TLIB_ONLY=true
131          shift $((OPTIND-1))
132          TLIB_ARCH=$1
133          OPTIND=2
134          ;;
135        "tlib-export-compile-commands")
136          if [ -z $TLIB_ARCH ]; then
137              echo "--tlib-export-compile-commands requires --tlib-arch begin set"
138              exit 1
139          fi
140          TLIB_EXPORT_COMPILE_COMMANDS=true
141          ;;
142        "host-arch")
143          shift $((OPTIND-1))
144          if [ $1 == "aarch64" ] || [ $1 == "arm64" ]; then
145            HOST_ARCH="aarch64"
146          elif [ $1 == "i386" ] || [ $1 == "x86" ] || [ $1 == "x86_64" ]; then
147            HOST_ARCH="i386"
148          else
149            echo "host architecture $1 not supported. Supported architectures are i386 and aarch64"
150            exit 1
151          fi
152          OPTIND=2
153          ;;
154        "skip-dotnet-target-generation")
155          GENERATE_DOTNET_BUILD_TARGET=false
156          ;;
157        *)
158          print_help
159          exit 1
160          ;;
161      esac
162      ;;
163    \?)
164      print_help
165      exit 1
166      ;;
167  esac
168done
169shift "$((OPTIND-1))"
170PARAMS+=(
171  # By default use CC as Compiler- and LinkerPath, and AR as ArPath
172  ${CC:+"p:CompilerPath=$CC"}
173  ${CC:+"p:LinkerPath=$CC"}
174  ${AR:+"p:ArPath=$AR"}
175  # But allow users to override it
176  "$@"
177)
178
179if [ -n "${PLATFORM:-}" ]
180then
181    echo "PLATFORM environment variable is currently set to: >>$PLATFORM<<"
182    echo "This might cause problems during the build."
183    echo "Please clear it with:"
184    echo ""
185    echo "    unset PLATFORM"
186    echo ""
187    echo " and run the build script again."
188
189    exit 1
190fi
191
192# We can only update parts of this repository if Renode is built from within the git tree
193if [ ! -e .git ]
194then
195  SKIP_FETCH=true
196  UPDATE_SUBMODULES=false
197fi
198
199if $SKIP_FETCH
200then
201  echo "Skipping init/update of submodules"
202else
203  # Update submodules if not initialized or if requested by the user
204  # Warn if not updating, but unclean
205  # Disabling -e to allow grep to fail
206  set +e
207  git submodule status --recursive | grep -q "^-"
208  SUBMODULES_NOT_INITED=$?
209
210  git submodule status --recursive | grep -q "^+"
211  SUBMODULES_NOT_CLEAN=$?
212  set -e
213  if $UPDATE_SUBMODULES || [ $SUBMODULES_NOT_INITED -eq 0 ]
214  then
215      echo "Updating submodules..."
216      git submodule update --init --recursive
217  elif [ $SUBMODULES_NOT_CLEAN -eq 0 ]
218  then
219      echo "Submodules are not updated. Use -s to force update."
220  fi
221fi
222
223. "${ROOT_PATH}/tools/common.sh"
224
225if $SKIP_FETCH
226then
227  echo "Skipping library fetch"
228else
229  "${ROOT_PATH}"/tools/building/fetch_libraries.sh
230fi
231
232if $HEADLESS
233then
234    BUILD_TARGET=Headless
235    PARAMS+=(p:GUI_DISABLED=true)
236elif $ON_WINDOWS
237then
238    BUILD_TARGET=Windows
239    TFM="$TFM-windows10.0.17763.0"
240else
241    BUILD_TARGET=Mono
242fi
243
244# Set correct RID
245if $ON_LINUX; then
246    RID="linux-x64"
247    if [[ $HOST_ARCH == "aarch64" ]]; then
248        RID="linux-arm64"
249    fi
250elif $ON_OSX; then
251    RID="osx-x64"
252    if [[ $HOST_ARCH == "aarch64" ]]; then
253        RID="osx-arm64"
254    fi
255elif $ON_WINDOWS; then
256    RID="win-x64"
257fi
258
259if [[ $GENERATE_DOTNET_BUILD_TARGET = true ]]; then
260  if $ON_WINDOWS; then
261    # CsWinRTAotOptimizerEnabled is disabled due to a bug in dotnet-sdk.
262    # See: https://github.com/dotnet/sdk/issues/44026
263    OS_SPECIFIC_TARGET_OPTS='<CsWinRTAotOptimizerEnabled>false</CsWinRTAotOptimizerEnabled>'
264  fi
265
266cat <<EOF > "$(get_path "$PWD/Directory.Build.targets")"
267<Project>
268  <PropertyGroup>
269    <TargetFrameworks>$TFM</TargetFrameworks>
270    ${OS_SPECIFIC_TARGET_OPTS:+${OS_SPECIFIC_TARGET_OPTS}}
271  </PropertyGroup>
272</Project>
273EOF
274
275fi
276
277if $NET
278then
279  export DOTNET_CLI_TELEMETRY_OPTOUT=1
280  CS_COMPILER="dotnet build"
281  TARGET="`get_path \"$PWD/Renode_NET.sln\"`"
282  BUILD_TYPE="dotnet"
283else
284  TARGET="`get_path \"$PWD/Renode.sln\"`"
285  BUILD_TYPE="mono"
286fi
287
288OUT_BIN_DIR="$(get_path "output/bin/${CONFIGURATION}")"
289BUILD_TYPE_FILE=$(get_path "${OUT_BIN_DIR}/build_type")
290
291# Verify Mono and mcs version on Linux and macOS
292if ! $ON_WINDOWS && ! $NET
293then
294    if ! [ -x "$(command -v mcs)" ]
295    then
296        MINIMUM_MONO=`get_min_mono_version`
297        echo "mcs not found. Renode requires Mono $MINIMUM_MONO or newer. Please refer to documentation for installation instructions. Exiting!"
298        exit 1
299    fi
300
301    verify_mono_version
302fi
303
304# Copy properties file according to the running OS
305mkdir -p "$OUTPUT_DIRECTORY"
306if [ -n "${CUSTOM_PROP}" ]; then
307    PROP_FILE=$CUSTOM_PROP
308else
309    if $ON_OSX
310    then
311      PROP_FILE="${CURRENT_PATH:=.}/src/Infrastructure/src/Emulator/Cores/osx-properties.csproj"
312    elif $ON_LINUX
313    then
314      PROP_FILE="${CURRENT_PATH:=.}/src/Infrastructure/src/Emulator/Cores/linux-properties.csproj"
315    else
316      PROP_FILE="${CURRENT_PATH:=.}/src/Infrastructure/src/Emulator/Cores/windows-properties.csproj"
317    fi
318fi
319cp "$PROP_FILE" "$OUTPUT_DIRECTORY/properties.csproj"
320
321if ! $NET
322then
323  # Assets files are not deleted during `dotnet clean`, as it would confuse intellisense per comment in https://github.com/NuGet/Home/issues/7368#issuecomment-457411014,
324  # but we need to delete them to build Renode again for .NETFramework since `project.assets.json` doesn't play well if project files share the same directory.
325  # If `Renode_NET.sln` is picked for OmniSharp, it will trigger reanalysis of the project after removing assets files.
326  # We don't remove these files as part of `clean` target, because other intermediate files are well separated between .NET and .NETFramework
327  # and enforcing `clean` every time before rebuilding would slow down the build process on both frameworks.
328  find $ROOT_PATH -type f -name 'project.assets.json' -delete
329fi
330
331CORES_PATH="$ROOT_PATH/src/Infrastructure/src/Emulator/Cores"
332
333# clean instead of building
334if $CLEAN
335then
336    for project_dir in $(find "$(get_path "${ROOT_PATH}/src")" -iname '*.csproj' -exec dirname '{}' \;)
337    do
338      for dir in {bin,obj}/{Debug,Release}
339      do
340        output_dir="$(get_path "${project_dir}/${dir}")"
341        if [[ -d "${output_dir}" ]]
342        then
343          echo "Removing: ${output_dir}"
344          rm -rf "${output_dir}"
345        fi
346      done
347    done
348
349    # Manually clean the main output directory as it's location is non-standard
350    main_output_dir="$(get_path "${OUTPUT_DIRECTORY}/bin")"
351    if [[ -d "${main_output_dir}" ]]
352    then
353      echo "Removing: ${main_output_dir}"
354      rm -rf "${main_output_dir}"
355    fi
356    exit 0
357fi
358
359# Check if a full rebuild is needed
360if [[ -f "$BUILD_TYPE_FILE" ]]
361then
362  if [[ "$(cat "$BUILD_TYPE_FILE")" != "$BUILD_TYPE" ]]
363  then
364    echo "Attempted to build Renode in a different configuration than the previous build"
365    echo "Please run '$0 -c' to clean the previous build before continuing"
366    exit 1
367  fi
368fi
369
370# check weak implementations of core libraries
371pushd "$ROOT_PATH/tools/building" > /dev/null
372./check_weak_implementations.sh
373popd > /dev/null
374
375PARAMS+=(p:Configuration="${CONFIGURATION}${BUILD_TARGET}" p:GenerateFullPaths=true p:Platform="\"$BUILD_PLATFORM\"")
376
377# Paths for tlib
378CORES_BUILD_PATH="$CORES_PATH/obj/$CONFIGURATION"
379CORES_BIN_PATH="$CORES_PATH/bin/$CONFIGURATION"
380
381# Cmake generator, handled in their own variable since the names contain spaces
382if $ON_WINDOWS
383then
384    CMAKE_GEN="-GMinGW Makefiles"
385else
386    CMAKE_GEN="-GUnix Makefiles"
387fi
388
389# Macos architecture flags, to make rosetta work properly
390if $ON_OSX
391then
392  CMAKE_COMMON+=" -DCMAKE_OSX_ARCHITECTURES=x86_64"
393  if [ $HOST_ARCH == "aarch64" ]; then
394    CMAKE_COMMON+=" -DCMAKE_OSX_ARCHITECTURES=arm64"
395  fi
396fi
397
398# This list contains all cores that will be built.
399# If you are adding a new core or endianness add it here to have the correct tlib built
400CORES=(arm.le arm.be arm64.le arm-m.le arm-m.be ppc.le ppc.be ppc64.le ppc64.be i386.le x86_64.le riscv.le riscv64.le sparc.le sparc.be xtensa.le)
401
402# if '--tlib-arch' was used - pick the first matching one
403if [[ ! -z $TLIB_ARCH ]]; then
404  NONE_MATCHED=true
405  for potential_match in "${CORES[@]}"; do
406    if [[ $potential_match == "$TLIB_ARCH"* ]]; then
407      CORES=("$potential_match")
408      echo "Compiling tlib for $potential_match"
409      NONE_MATCHED=false
410      break
411    fi
412  done
413  if $NONE_MATCHED ; then
414    echo "Failed to match any tlib arch"
415    exit 1
416  fi
417fi
418
419# build tlib
420for core_config in "${CORES[@]}"
421do
422    CORE="$(echo $core_config | cut -d '.' -f 1)"
423    ENDIAN="$(echo $core_config | cut -d '.' -f 2)"
424    BITS=32
425    # Check if core is 64-bit
426    if [[ $CORE =~ "64" ]]; then
427      BITS=64
428    fi
429    # Core specific flags to cmake
430    CMAKE_CONF_FLAGS="-DTARGET_ARCH=$CORE -DTARGET_WORD_SIZE=$BITS -DCMAKE_BUILD_TYPE=$CONFIGURATION"
431    CORE_DIR=$CORES_BUILD_PATH/$CORE/$ENDIAN
432    mkdir -p $CORE_DIR
433    pushd "$CORE_DIR" > /dev/null
434    if [[ $ENDIAN == "be" ]]; then
435        CMAKE_CONF_FLAGS+=" -DTARGET_WORDS_BIGENDIAN=1"
436    fi
437    if [[ "$TLIB_EXPORT_COMPILE_COMMANDS" = true ]]; then
438        CMAKE_CONF_FLAGS+=" -DCMAKE_EXPORT_COMPILE_COMMANDS=1"
439    fi
440    cmake "$CMAKE_GEN" $CMAKE_COMMON $CMAKE_CONF_FLAGS -DHOST_ARCH=$HOST_ARCH $CORES_PATH
441    cmake --build . -j"$(nproc)"
442    CORE_BIN_DIR=$CORES_BIN_PATH/lib
443    mkdir -p $CORE_BIN_DIR
444    if $ON_OSX; then
445        # macos `cp` does not have the -u flag
446        cp -v tlib/*.so $CORE_BIN_DIR/
447    else
448        cp -u -v tlib/*.so $CORE_BIN_DIR/
449    fi
450    # copy compile_commands.json to tlib directory
451    if [[ "$TLIB_EXPORT_COMPILE_COMMANDS" = true ]]; then
452       command cp -v -f $CORE_DIR/compile_commands.json $CORES_PATH/tlib/
453    fi
454    popd > /dev/null
455done
456
457if $TLIB_ONLY
458then
459    exit 0
460fi
461
462# build
463eval "$CS_COMPILER $(build_args_helper "${PARAMS[@]}") $TARGET"
464echo -n "$BUILD_TYPE" > "$BUILD_TYPE_FILE"
465
466# copy llvm library
467LLVM_LIB="libllvm-disas"
468if [[ $HOST_ARCH == "aarch64" ]]; then
469  # aarch64 host binaries have a different name
470  LLVM_LIB="libllvm-disas-aarch64"
471fi
472if [[ "${DETECTED_OS}" == "windows" ]]; then
473  LIB_EXT="dll"
474elif [[ "${DETECTED_OS}" == "osx" ]]; then
475  LIB_EXT="dylib"
476else
477  LIB_EXT="so"
478fi
479cp lib/resources/llvm/$LLVM_LIB.$LIB_EXT $OUT_BIN_DIR/libllvm-disas.$LIB_EXT
480
481# build packages after successful compilation
482params=""
483
484if [ $CONFIGURATION == "Debug" ]
485then
486    params="$params -d"
487fi
488
489if [ -n "$EXPORT_DIRECTORY" ]
490then
491    if [ "${DETECTED_OS}" != "linux" ]
492    then
493        echo "Custom output directory is currently available on Linux only"
494        exit 1
495    fi
496
497    $ROOT_PATH/tools/packaging/export_${DETECTED_OS}_workdir.sh $EXPORT_DIRECTORY $params
498    echo "Renode built to $EXPORT_DIRECTORY"
499fi
500
501if $PACKAGES && $NIGHTLY
502then
503    params="$params -n"
504fi
505
506if $PACKAGES
507then
508    if $NET
509    then
510        # dotnet package on linux uses a separate script
511        if $ON_LINUX
512        then
513            # maxcpucount:1 to avoid an error with multithreaded publish
514            eval "dotnet publish -maxcpucount:1 -f $TFM --self-contained false $(build_args_helper "${PARAMS[@]}") $TARGET"
515            export RID TFM
516            $ROOT_PATH/tools/packaging/make_linux_dotnet_package.sh $params
517            # Source package bundles nuget dependencies required for building the dotnet version of Renode
518            # so it can only be built when using dotnet. The generated package can also be used with Mono/.NETFramework
519            $ROOT_PATH/tools/packaging/make_source_package.sh $params
520        elif $ON_WINDOWS && ! $PORTABLE
521        then
522            # No Non portable dotnet package on windows yet
523            echo "Only portable dotnet packages are supported on windows. Rerun build.sh with -t flag to build portable"
524            exit 1
525        elif $ON_OSX
526        then
527            eval "dotnet publish -maxcpucount:1 -f $TFM --self-contained false $(build_args_helper "${PARAMS[@]}") $TARGET"
528            export RID TFM
529            $ROOT_PATH/tools/packaging/make_osx_dotnet_package.sh $params
530        fi
531    else
532        $ROOT_PATH/tools/packaging/make_${DETECTED_OS}_packages.sh $params
533    fi
534fi
535
536if $PORTABLE
537then
538    PARAMS+=(p:PORTABLE=true)
539    if $NET
540    then
541        # maxcpucount:1 to avoid an error with multithreaded publish
542	echo "RID = $RID"
543        eval "dotnet publish -maxcpucount:1 -r $RID -f $TFM --self-contained true $(build_args_helper "${PARAMS[@]}") $TARGET"
544        export RID TFM
545        $ROOT_PATH/tools/packaging/make_${DETECTED_OS}_portable_dotnet.sh $params
546    else
547        if $ON_LINUX
548        then
549            $ROOT_PATH/tools/packaging/make_linux_portable.sh $params
550        else
551            echo "Portable packages for Mono are only available on Linux. Exiting!"
552            exit 1
553        fi
554    fi
555fi
556