diff --git a/.github/actions/build_android/action.yml b/.github/actions/build_android/action.yml
new file mode 100644
index 0000000..7c78d53
--- /dev/null
+++ b/.github/actions/build_android/action.yml
@@ -0,0 +1,34 @@
+name: 'Build Android Library'
+description: 'Builds the Android library using Fastlane'
+
+runs:
+ using: 'composite'
+
+ steps:
+ - name: Set up JDK
+ uses: actions/setup-java@v3
+ with:
+ distribution: 'temurin'
+ java-version: 17
+
+ - name: Set up Ruby
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: '2.7'
+
+ - name: Install Fastlane
+ run: gem install fastlane
+ shell: bash
+
+ - name: Setup Node
+ uses: actions/setup-node@v3
+ with:
+ node-version-file: .nvmrc
+
+ - name: Build Android Library with Fastlane
+ run: cd android && fastlane android build_mendix_native
+ shell: bash
+
+ - name: Copy Android Library to Shared Directory
+ run: mkdir -p ${{ github.workspace }}/shared/libs/android && cp ./artifacts/aar/mendixnative-release.aar ${{ github.workspace }}/shared/libs/android/mendixnative-release.aar
+ shell: bash
diff --git a/.github/actions/build_ios/action.yml b/.github/actions/build_ios/action.yml
new file mode 100644
index 0000000..800ee2b
--- /dev/null
+++ b/.github/actions/build_ios/action.yml
@@ -0,0 +1,23 @@
+name: 'Build iOS Library'
+description: 'Builds the iOS library using Fastlane'
+
+runs:
+ using: 'composite'
+
+ steps:
+ - name: Install Fastlane
+ run: gem install fastlane
+ shell: bash
+
+ - name: Setup Node
+ uses: actions/setup-node@v3
+ with:
+ node-version-file: .nvmrc
+
+ - name: Build iOS Library with Fastlane
+ run: cd ios && fastlane ios build_mendix_native
+ shell: bash
+
+ - name: Copy iOS Library to Shared Directory
+ run: mkdir -p ${{ github.workspace }}/shared/libs/ios && cp -R ./ios/build/mendixnative/mendixnative.xcframework ${{ github.workspace }}/shared/libs/ios/mendixnative.xcframework
+ shell: bash
diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml
deleted file mode 100644
index fb98c79..0000000
--- a/.github/actions/setup/action.yml
+++ /dev/null
@@ -1,27 +0,0 @@
-name: Setup
-description: Setup Node.js and install dependencies
-
-runs:
- using: composite
- steps:
- - name: Setup Node.js
- uses: actions/setup-node@v3
- with:
- node-version-file: .nvmrc
-
- - name: Cache dependencies
- id: yarn-cache
- uses: actions/cache@v3
- with:
- path: |
- **/node_modules
- .yarn/install-state.gz
- key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }}-${{ hashFiles('**/package.json', '!node_modules/**') }}
- restore-keys: |
- ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }}
- ${{ runner.os }}-yarn-
-
- - name: Install dependencies
- if: steps.yarn-cache.outputs.cache-hit != 'true'
- run: yarn install --immutable
- shell: bash
diff --git a/.github/workflows/BuildLibrary.yml b/.github/workflows/BuildLibrary.yml
new file mode 100644
index 0000000..6756581
--- /dev/null
+++ b/.github/workflows/BuildLibrary.yml
@@ -0,0 +1,49 @@
+name: 'Build @mendix/native'
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+ branches:
+ - main
+
+jobs:
+ build-android:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v3
+
+ - name: Build Android Library
+ uses: ./.github/actions/build_android
+
+ build-ios:
+ runs-on: macos-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v3
+
+ - name: Build iOS Library
+ uses: ./.github/actions/build_ios
+
+ create-release:
+ needs: [build-android, build-ios]
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v3
+
+ - name: Zip Libraries
+ run: |
+ cd ${{ github.workspace }}/shared/libs
+ zip -r libraries.zip .
+
+ - name: Upload Libraries to Artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: mendixnative-archive
+ path: ${{ github.workspace }}/shared/libs/libraries.zip
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
deleted file mode 100644
index 6297ef1..0000000
--- a/.github/workflows/ci.yml
+++ /dev/null
@@ -1,154 +0,0 @@
-name: CI
-on:
- push:
- branches:
- - main
- pull_request:
- branches:
- - main
-
-jobs:
- lint:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v3
-
- - name: Setup
- uses: ./.github/actions/setup
-
- - name: Lint files
- run: yarn lint
-
- - name: Typecheck files
- run: yarn typecheck
-
- test:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v3
-
- - name: Setup
- uses: ./.github/actions/setup
-
- - name: Run unit tests
- run: yarn test --maxWorkers=2 --coverage
-
- build-library:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v3
-
- - name: Setup
- uses: ./.github/actions/setup
-
- - name: Build package
- run: yarn prepare
-
- build-android:
- runs-on: ubuntu-latest
- env:
- TURBO_CACHE_DIR: .turbo/android
- steps:
- - name: Checkout
- uses: actions/checkout@v3
-
- - name: Setup
- uses: ./.github/actions/setup
-
- - name: Cache turborepo for Android
- uses: actions/cache@v3
- with:
- path: ${{ env.TURBO_CACHE_DIR }}
- key: ${{ runner.os }}-turborepo-android-${{ hashFiles('yarn.lock') }}
- restore-keys: |
- ${{ runner.os }}-turborepo-android-
-
- - name: Check turborepo cache for Android
- run: |
- TURBO_CACHE_STATUS=$(node -p "($(yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}" --dry=json)).tasks.find(t => t.task === 'build:android').cache.status")
-
- if [[ $TURBO_CACHE_STATUS == "HIT" ]]; then
- echo "turbo_cache_hit=1" >> $GITHUB_ENV
- fi
-
- - name: Install JDK
- if: env.turbo_cache_hit != 1
- uses: actions/setup-java@v3
- with:
- distribution: 'zulu'
- java-version: '17'
-
- - name: Finalize Android SDK
- if: env.turbo_cache_hit != 1
- run: |
- /bin/bash -c "yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses > /dev/null"
-
- - name: Cache Gradle
- if: env.turbo_cache_hit != 1
- uses: actions/cache@v3
- with:
- path: |
- ~/.gradle/wrapper
- ~/.gradle/caches
- key: ${{ runner.os }}-gradle-${{ hashFiles('example/android/gradle/wrapper/gradle-wrapper.properties') }}
- restore-keys: |
- ${{ runner.os }}-gradle-
-
- - name: Build example for Android
- env:
- JAVA_OPTS: "-XX:MaxHeapSize=6g"
- run: |
- yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}"
-
- build-ios:
- runs-on: macos-14
- env:
- TURBO_CACHE_DIR: .turbo/ios
- steps:
- - name: Checkout
- uses: actions/checkout@v3
-
- - name: Setup
- uses: ./.github/actions/setup
-
- - name: Cache turborepo for iOS
- uses: actions/cache@v3
- with:
- path: ${{ env.TURBO_CACHE_DIR }}
- key: ${{ runner.os }}-turborepo-ios-${{ hashFiles('yarn.lock') }}
- restore-keys: |
- ${{ runner.os }}-turborepo-ios-
-
- - name: Check turborepo cache for iOS
- run: |
- TURBO_CACHE_STATUS=$(node -p "($(yarn turbo run build:ios --cache-dir="${{ env.TURBO_CACHE_DIR }}" --dry=json)).tasks.find(t => t.task === 'build:ios').cache.status")
-
- if [[ $TURBO_CACHE_STATUS == "HIT" ]]; then
- echo "turbo_cache_hit=1" >> $GITHUB_ENV
- fi
-
- - name: Cache cocoapods
- if: env.turbo_cache_hit != 1
- id: cocoapods-cache
- uses: actions/cache@v3
- with:
- path: |
- **/ios/Pods
- key: ${{ runner.os }}-cocoapods-${{ hashFiles('example/ios/Podfile.lock') }}
- restore-keys: |
- ${{ runner.os }}-cocoapods-
-
- - name: Install cocoapods
- if: env.turbo_cache_hit != 1 && steps.cocoapods-cache.outputs.cache-hit != 'true'
- run: |
- cd example/ios
- pod install
- env:
- NO_FLIPPER: 1
-
- - name: Build example for iOS
- run: |
- yarn turbo run build:ios --cache-dir="${{ env.TURBO_CACHE_DIR }}"
diff --git a/.gitignore b/.gitignore
index d741fcd..263fdf7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -69,9 +69,6 @@ android/keystores/debug.keystore
# Turborepo
.turbo/
-# generated by bob
-lib/
-
# fastlane specific
**/fastlane/report.xml
@@ -86,3 +83,5 @@ lib/
# Fastlane.swift runner binary
**/fastlane/FastlaneRunner
+
+/artifacts/*
diff --git a/ios/fastlane/Fastfile b/ios/fastlane/Fastfile
index 28638a6..ba52e73 100644
--- a/ios/fastlane/Fastfile
+++ b/ios/fastlane/Fastfile
@@ -8,9 +8,9 @@ fastlane_version "2.213.0"
default_platform :ios
platform :ios do
-
+
desc "Build a new version of MendixNative lib"
- lane :build_mendixnative do |options|
+ lane :build_mendix_native do |options|
sh("npm", "ci", "--legacy-peer-deps")
cocoapods
diff --git a/ios/fastlane/report.xml b/ios/fastlane/report.xml
deleted file mode 100644
index c048edb..0000000
--- a/ios/fastlane/report.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/lib/.gitignore b/lib/.gitignore
new file mode 100644
index 0000000..149f722
--- /dev/null
+++ b/lib/.gitignore
@@ -0,0 +1,5 @@
+.npmrc
+androidlib/*.aar
+ios/mendixnative.xcframework/*
+node_modules
+artifacts
diff --git a/lib/.release-it.js b/lib/.release-it.js
new file mode 100644
index 0000000..5e47f1a
--- /dev/null
+++ b/lib/.release-it.js
@@ -0,0 +1,34 @@
+module.exports = {
+ npm: {
+ publish: true,
+ },
+ git: {
+ push: false,
+ requireUpstream: false,
+ tag: false,
+ requireCleanWorkingDir: false,
+ commitMessage:
+ "[RELEASE] Bump @mendix/native library version to v${version}",
+ },
+ publishConfig: {
+ registry:
+ "https://nexus.staging.not-rnd.mendix.com/repository/npm-hosted",
+ },
+ plugins: {
+ "@release-it/keep-a-changelog": {
+ filename: "../CHANGELOG.mendixnative.md",
+ strictLatest: false,
+ keepUnreleased: process.env.VERSION === "prerelease",
+ addUnreleased: true,
+ },
+ },
+ hooks: {
+ "before:init": [
+ "git fetch origin",
+ "git checkout " + process.env.BRANCH,
+ "git pull --rebase origin " + process.env.BRANCH + " --autostash",
+ ],
+ "before:git:release": ["git add ../CHANGELOG.mendixnative.md"],
+ "after:release": ["git push origin " + process.env.BRANCH],
+ },
+};
diff --git a/lib/LICENSE.md b/lib/LICENSE.md
new file mode 100644
index 0000000..fde8b1b
--- /dev/null
+++ b/lib/LICENSE.md
@@ -0,0 +1,19 @@
+Copyright (c) Mendix
+
+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.
diff --git a/lib/androidlib/build.gradle b/lib/androidlib/build.gradle
new file mode 100644
index 0000000..7028549
--- /dev/null
+++ b/lib/androidlib/build.gradle
@@ -0,0 +1,2 @@
+configurations.maybeCreate("default")
+artifacts.add("default", file("mendixnative-release.aar"))
diff --git a/lib/androidlib/mendix.gradle b/lib/androidlib/mendix.gradle
new file mode 100644
index 0000000..693da9b
--- /dev/null
+++ b/lib/androidlib/mendix.gradle
@@ -0,0 +1,438 @@
+import groovy.json.JsonSlurper
+
+def LOG_PREFIX = ":Mendix: "
+
+def rootDir = buildscript.sourceFile.toString().split("node_modules")[0]
+def cliBinPath = "${rootDir}/node_modules/.bin/react-native${System.properties['os.name'].toLowerCase().contains('windows') ? ".cmd" : ""}"
+
+def generatedFilePackage = "com.mendix.nativetemplate"
+def mainActivityObserverFileName = "MendixActivityObserver.java"
+def mainActivityObserverTemplate = """package $generatedFilePackage;
+
+import android.content.Context;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.OnLifecycleEvent;
+{{imports}}
+
+public class MendixActivityObserver implements LifecycleObserver {
+ private final Context context;
+
+ public MendixActivityObserver(Context activity) {
+ this.context = activity;
+ }
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
+ void onCreate() {
+ {{onCreate}}
+ }
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
+ void onResume() {
+ {{onResume}}
+ }
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_START)
+ void onStart() {
+ {{onStart}}
+ }
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
+ void onPause() {
+ {{onPause}}
+ }
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
+ void onStop() {
+ {{onStop}}
+ }
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
+ void onDestroy() {
+ {{onDestroy}}
+ }
+}
+"""
+def mendixPackageListFileName = "MendixPackageList.java"
+def mendixPackageListTemplate = """package $generatedFilePackage;
+
+import android.app.Application;
+import android.content.Context;
+import android.content.res.Resources;
+
+import com.facebook.react.ReactPackage;
+import com.facebook.react.shell.MainPackageConfig;
+import com.facebook.react.shell.MainReactPackage;
+import java.util.Arrays;
+import java.util.ArrayList;
+
+{{imports}}
+
+public class MendixPackageList {
+ private Application application;
+
+ public MendixPackageList(Application application) {
+ this.application = application;
+ }
+
+ private Resources getResources() {
+ return this.getApplication().getResources();
+ }
+
+ private Application getApplication() {
+ return this.application;
+ }
+
+ private Context getApplicationContext() {
+ return this.getApplication().getApplicationContext();
+ }
+
+ public ArrayList getPackages() {
+ return new ArrayList<>(Arrays.asList(
+ {{packageClassInstances}}
+ ));
+ }
+}
+"""
+
+class MendixModules {
+ private static String LINE_ENDING_CHAR = "\n"
+
+ private String cliBinPath
+ private String rootDir
+ private String logPrefix
+ private Logger logger
+ private ArrayList> reactNativeModules
+ private Map dependenciesConfig = [:]
+ private File capabilitiesConfigFile
+ private File projectCapabilitiesFile
+ private File nodeModulesDependenciesConfigFile
+
+ MendixModules(File capabilitiesConfigFile, File nodeModulesDependenciesConfigFile, File projectCapabilitiesFile, String cliBinPath, String rootDir, Logger logger, String logPrefix) {
+ this.logger = logger
+ this.rootDir = rootDir
+ this.cliBinPath = cliBinPath
+ this.logPrefix = logPrefix
+ this.capabilitiesConfigFile = capabilitiesConfigFile
+ this.nodeModulesDependenciesConfigFile = nodeModulesDependenciesConfigFile
+ this.projectCapabilitiesFile = projectCapabilitiesFile
+
+ def (nativeModules) = this.getReactNativeConfig()
+ this.reactNativeModules = nativeModules
+ parseDependenciesConfig()
+ }
+
+ void printDependencies() {
+ this.reactNativeModules.each {
+ logDebug(it["name"])
+ }
+ }
+
+ void parseDependenciesConfig() {
+ def dependenciesConfig = [:]
+ def capabilitiesConfig = [:]
+
+ try {
+ capabilitiesConfig = new JsonSlurper().parse(this.capabilitiesConfigFile)
+ def projectCapabilities = new JsonSlurper().parse(this.projectCapabilitiesFile)
+ capabilitiesConfig.retainAll { capabilityConfig ->
+ projectCapabilities.find { enabledCapability ->
+ enabledCapability.key == capabilityConfig.key && enabledCapability.value == true
+ } && capabilityConfig.value["android"] != null
+ }
+ } catch (ignored) {
+ this.logLifecycle("Failed parsing the capabilities file. Error?")
+ }
+
+ if (this.nodeModulesDependenciesConfigFile.exists()) {
+ try {
+ dependenciesConfig = new JsonSlurper().parse(this.nodeModulesDependenciesConfigFile)
+ (dependenciesConfig as Map).retainAll { dependencyConfig ->
+ this.reactNativeModules.find { nativeModule ->
+ nativeModule.get("name") == dependencyConfig.key
+ } && dependencyConfig.value["android"] != null
+ }
+ } catch (ignored) {
+ this.logLifecycle("Failed parsing the configuration for unlinked node_modules. Error?")
+ }
+ }
+
+ this.dependenciesConfig = capabilitiesConfig + dependenciesConfig
+ printDependencies()
+ }
+
+ void generateMainActivityObserver(File outDir, String fileName, String template) {
+ def activityImports = []
+ def activityOnCreateEntries = []
+ def activityOnStartEntries = []
+ def activityOnResumeEntries = []
+ def activityOnPauseEntries = []
+ def activityOnStopEntries = []
+ def activityOnDestroyEntries = []
+
+ dependenciesConfig.each {
+ def mainActivityDelegateEntry = it.value["android"]["MainActivity"]
+ if (!mainActivityDelegateEntry)
+ return
+
+ def imports = mainActivityDelegateEntry.get("imports")
+ if (imports)
+ activityImports.addAll(imports)
+
+ def onCreateEntries = mainActivityDelegateEntry.get("onCreate")
+ if (onCreateEntries)
+ activityOnCreateEntries.addAll(onCreateEntries)
+
+ def onStartEntries = mainActivityDelegateEntry.get("onStart")
+ if (onStartEntries)
+ activityOnStartEntries.addAll(onStartEntries)
+
+ def onResumeEntries = mainActivityDelegateEntry.get("onResume")
+ if (onResumeEntries)
+ activityOnResumeEntries.addAll(onResumeEntries)
+
+ def onPauseEntries = mainActivityDelegateEntry.get("onPause")
+ if (onPauseEntries)
+ activityOnPauseEntries.addAll(onPauseEntries)
+
+ def onStopEntries = mainActivityDelegateEntry.get("onStop")
+ if (onStopEntries)
+ activityOnStopEntries.addAll(onStopEntries)
+
+ def onDestroyEntries = mainActivityDelegateEntry.get("onDestroy")
+ if (onDestroyEntries)
+ activityOnDestroyEntries.addAll(onDestroyEntries)
+ }
+
+ String CODE_PADDING = "${LINE_ENDING_CHAR} "
+ String generatedFileContents = template
+ .replace("{{imports}}", activityImports.join(LINE_ENDING_CHAR))
+ .replace("{{onCreate}}", activityOnCreateEntries.join(CODE_PADDING))
+ .replace("{{onStart}}", activityOnStartEntries.join(CODE_PADDING))
+ .replace("{{onResume}}", activityOnResumeEntries.join(CODE_PADDING))
+ .replace("{{onPause}}", activityOnPauseEntries.join(CODE_PADDING))
+ .replace("{{onStop}}", activityOnStopEntries.join(CODE_PADDING))
+ .replace("{{onDestroy}}", activityOnDestroyEntries.join(CODE_PADDING))
+
+ outDir.mkdirs()
+ new FileTreeBuilder(outDir).file(fileName).newWriter().withWriter {
+ w ->
+ w << generatedFileContents
+ }
+ }
+
+ void generateMendixPackageList(File outDir, String fileName, String template) {
+ String CODE_PADDING = "${LINE_ENDING_CHAR} "
+ def imports = []
+ def entries = []
+ def entrySeparator = "," + CODE_PADDING
+ dependenciesConfig.each {
+ def packageListEntry = it.value["android"]["packageListEntries"]
+ if (packageListEntry) {
+ def importsEntry = packageListEntry["imports"]
+ def packageClassInstances = packageListEntry["packageClassInstances"]
+ if (importsEntry)
+ imports.addAll(importsEntry)
+ if (packageClassInstances)
+ entries.addAll(packageClassInstances)
+ }
+ }
+
+ String generatedFileContents = template.replace("{{imports}}", imports.join(CODE_PADDING)).replace("{{packageClassInstances}}", entries.join(entrySeparator))
+
+ outDir.mkdirs()
+ new FileTreeBuilder(outDir).file(fileName).newWriter().withWriter {
+ w ->
+ w << generatedFileContents
+ }
+ }
+
+ void addClassPaths(Project project) {
+ project.buildscript {
+ dependencies {
+ dependenciesConfig.each {
+ def gradle = (it.value as Object)["android"]["gradle"]
+ if (!gradle) {
+ return
+ }
+ def customClassPaths = gradle.get("classpaths") as ArrayList
+ customClassPaths.each { customClassPath ->
+ this.logLifecycle("Adding classPath ${customClassPath}")
+ classpath(customClassPath)
+ }
+ }
+ }
+ }
+ }
+
+ void addExtraDependencies(Project project) {
+ project.dependencies {
+ dependenciesConfig.each {
+ def dependencies = it.value["android"]["externalDependencies"] as ArrayList
+ dependencies.each { dependency ->
+ this.logLifecycle("Registering extra library ${dependency}")
+ implementation(dependency)
+ }
+ }
+ }
+ }
+
+ void addAndroidPlugins(Project project) {
+ dependenciesConfig.each {
+ def gradleConfig = it.value["android"]["gradle"]
+ if (!gradleConfig)
+ return
+
+ def dependencies = gradleConfig["plugins"] as ArrayList
+ if (!dependencies)
+ return
+
+ dependencies.each { plugin ->
+ this.logLifecycle("Adding plugin ${plugin}")
+ project.getPluginManager().apply(plugin)
+ }
+ }
+ }
+
+ void logDebug(String message) {
+ this.logger.debug("${this.logPrefix}${message}")
+ }
+
+ void logLifecycle(String message) {
+ this.logger.lifecycle("${this.logPrefix}${message}")
+ }
+
+ void logError(String message) {
+ this.logger.error("${this.logPrefix}${message}")
+ }
+
+ /**
+ * Runs a specified command using Runtime exec() in a specified directory.
+ * Throws when the command result is empty.
+ */
+ String getCommandOutput(String[] command, File directory) {
+ try {
+ def output = ""
+ def cmdProcess = Runtime.getRuntime().exec(command, null, directory)
+ def bufferedReader = new BufferedReader(new InputStreamReader(cmdProcess.getInputStream()))
+ def buff = ""
+ def readBuffer = new StringBuffer()
+ while ((buff = bufferedReader.readLine()) != null) {
+ readBuffer.append(buff)
+ }
+ output = readBuffer.toString()
+ if (!output) {
+ this.logger.error("${logPrefix}Unexpected empty result of running '${command}' command.")
+ def bufferedErrorReader = new BufferedReader(new InputStreamReader(cmdProcess.getErrorStream()))
+ def errBuff = ""
+ def readErrorBuffer = new StringBuffer()
+ while ((errBuff = bufferedErrorReader.readLine()) != null) {
+ readErrorBuffer.append(errBuff)
+ }
+ throw new Exception(readErrorBuffer.toString())
+ }
+ return output
+ } catch (Exception exception) {
+ this.logError("Running '${command}' command failed.")
+ throw exception
+ }
+ }
+
+ /**
+ * Runs a process to call the React Native CLI Config command and parses the output
+ */
+ ArrayList> getReactNativeConfig() {
+ if (this.reactNativeModules != null) return this.reactNativeModules
+
+ ArrayList> reactNativeModules = new ArrayList>()
+
+ String[] reactNativeConfigCommand = [this.cliBinPath, "config"]
+ def reactNativeConfigOutput = this.getCommandOutput(reactNativeConfigCommand, new File(this.rootDir))
+
+ def json
+ try {
+ json = new JsonSlurper().parseText(reactNativeConfigOutput)
+ } catch (Exception exception) {
+ throw new Exception("Calling `${reactNativeConfigCommand}` finished with an exception. Error message: ${exception.toString()}. Output: ${reactNativeConfigOutput}");
+ }
+ def dependencies = json["dependencies"]
+ def project = json["project"]["android"]
+
+ if (project == null) {
+ throw new Exception("React Native CLI failed to determine Android project configuration. This is likely due to misconfiguration. Config output:\n${json.toMapString()}")
+ }
+
+ dependencies.each { name, value ->
+ def platformsConfig = value["platforms"];
+ def androidConfig = platformsConfig["android"]
+
+ if (androidConfig != null && androidConfig["sourceDir"] != null) {
+ this.logger.info("${logPrefix}Automatically adding native module '${name}'")
+
+ HashMap reactNativeModuleConfig = new HashMap()
+ reactNativeModuleConfig.put("name", name)
+ reactNativeModuleConfig.put("nameCleansed", name.replaceAll('^@([\\w-]+)/', '$1_'))
+ reactNativeModuleConfig.put("androidSourceDir", androidConfig["sourceDir"])
+ reactNativeModuleConfig.put("packageInstance", androidConfig["packageInstance"])
+ reactNativeModuleConfig.put("packageImportPath", androidConfig["packageImportPath"])
+ this.logger.trace("${logPrefix}'${name}': ${reactNativeModuleConfig.toMapString()}")
+
+ reactNativeModules.add(reactNativeModuleConfig)
+ } else {
+ this.logger.info("${logPrefix}Skipping native module '${name}'")
+ }
+ }
+
+ return [reactNativeModules, json["project"]["android"]["packageName"]];
+ }
+}
+
+def generatedSrcDir = new File(buildDir, "generated/mendix/src/main/java")
+def generatedCodeDir = new File(generatedSrcDir, generatedFilePackage.replace('.', '/'))
+
+def capabilitiesConfig = new File("${rootDir}capabilities-setup-config.json")
+def unlinkedDependenciesConfigFile = new File("${rootDir}unlinked-dependency-config.json")
+def capabilitiesFile = new File("${rootDir}capabilities.android.json")
+def mendixModules = new MendixModules(capabilitiesConfig, unlinkedDependenciesConfigFile, capabilitiesFile, cliBinPath, rootDir, logger, LOG_PREFIX)
+
+def logLifecycle = { String message -> logger.lifecycle("${LOG_PREFIX}${message}") }
+
+
+ext.applyMendixGradle = { Project project ->
+ logLifecycle("Registering extra dependencies")
+ mendixModules.addExtraDependencies(project)
+
+ logLifecycle("Registering plugins")
+ mendixModules.addAndroidPlugins(project)
+ task generateMendixDependencies {
+ doLast {
+ logLifecycle("Executing Mendix Module Generator")
+ logLifecycle("App root: ${rootDir}")
+ logLifecycle("CLI path: ${cliBinPath}")
+
+ logLifecycle("Generating ${mainActivityObserverFileName}")
+ mendixModules.generateMainActivityObserver(generatedCodeDir, mainActivityObserverFileName, mainActivityObserverTemplate)
+
+ logLifecycle("Generating ${mendixPackageListFileName}")
+ mendixModules.generateMendixPackageList(generatedCodeDir, mendixPackageListFileName, mendixPackageListTemplate)
+ }
+ }
+
+ preBuild.dependsOn generateMendixDependencies
+
+ android {
+ sourceSets {
+ main {
+ java {
+ srcDirs += generatedSrcDir
+ }
+ }
+ }
+ }
+}
+
+ext.applyMendixClassPaths = { Project project ->
+ logLifecycle("Registering class paths")
+ mendixModules.addClassPaths(project)
+}
diff --git a/lib/ios/.keep b/lib/ios/.keep
new file mode 100644
index 0000000..e69de29
diff --git a/lib/package.json b/lib/package.json
new file mode 100644
index 0000000..2a96c4b
--- /dev/null
+++ b/lib/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "@mendix/native",
+ "version": "3.0.1",
+ "copyright": "© Mendix Technology BV. All rights reserved.",
+ "files": [
+ "androidlib/",
+ "ios/",
+ "LICENSE"
+ ],
+ "scripts": {
+ "release": "release-it"
+ },
+ "devDependencies": {
+ "release-it": "^15.1.0",
+ "@release-it/conventional-changelog": "^5.0.0",
+ "@release-it/keep-a-changelog": "^3.0.0"
+ }
+}