Skip to content

Commit

Permalink
Merge pull request #13 from gfanton/feat/add-android-libs
Browse files Browse the repository at this point in the history
feat: add android build
  • Loading branch information
iuricmp authored Jul 31, 2023
2 parents 0faa1bc + 5c60d9d commit 1da26d9
Show file tree
Hide file tree
Showing 13 changed files with 222 additions and 109 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,4 @@ yarn-error.log

# gomobile
ios/Frameworks
android/libs
1 change: 1 addition & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ __tests__

# Exclude frameworks
/ios/Frameworks
/android/libs

# Excluding yarn.lock causes the local build to fail.
# This is because the build is done on the host machine.
Expand Down
127 changes: 86 additions & 41 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,41 +1,70 @@
make_dir := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
default_deps := node_modules Makefile
SHELL := /bin/bash

### ARGS
OUTPUT_FRAMEWORK ?= $(make_dir)/ios/Frameworks/WeshnetCore.xcframework
PROTO_COMMIT_HASH ?= c72d5759847b4dedb5411c19485e1a37
# Define the directory that contains the current Makefile
make_dir := $(realpath $(dir $(abspath $(lastword $(MAKEFILE_LIST)))))
cache_dir := $(make_dir)/.cache

# commands
# Argument Defaults
IOS_OUTPUT_FRAMEWORK_DIR ?= $(make_dir)/ios/Frameworks
ANDROID_OUTPUT_LIBS_DIR ?= $(make_dir)/android/libs
PROTOCOLTYPES_COMMIT_HASH ?= c72d5759847b4dedb5411c19485e1a37
GO_BIND_BIN_DIR ?= $(cache_dir)/bind

all build: build.ios build.android
# IOS definitions
weshnetcore_xcframework := $(IOS_OUTPUT_FRAMEWORK_DIR)/WeshnetCore.xcframework

build.ios: generate $(OUTPUT_FRAMEWORK)
build.android: generate output/android/core.aar
# Android definitions
weshnetcore_aar := $(ANDROID_OUTPUT_LIBS_DIR)/WeshnetCore.aar
weshnetcore_jar := $(ANDROID_OUTPUT_LIBS_DIR)/WeshnetCore-sources.jar

# Utility definitions
pbjs := ./node_modules/.bin/pbjs
pbts := ./node_modules/.bin/pbts
gomobile := $(GO_BIND_BIN_DIR)/gomobile
gobind := $(GO_BIND_BIN_DIR)/gobind

# go files and dependencies
go_files := $(shell find . -iname '*.go')
go_deps := go.mod go.sum $(go_files)

# rewrite shell path
# this is mostly for gomobile to have the correct gobind in his path
PATH := $(GO_BIND_BIN_DIR):$(PATH)

# * Main commands

# `all` and `build` command builds everything (generate, build.ios, build.android)
all build: generate build.ios build.android

# Build iOS framework
build.ios: generate $(weshnetcore_xcframework)

# Build Android aar & jar
build.android: generate $(weshnetcore_aar) $(weshnetcore_jar)

# Generate API from protofiles
generate: api.generate

# Clean all generated files
clean: api.clean bind.clean

# Force clean (clean and remove node_modules)
fclean: clean
rm -rf node_modules

.PHONY: generate build.ios build.android fclean

## node
# - Node: Handle node_modules

node_modules: package.json yarn.lock
(yarn && touch $@) || true

## api
# - API : Handle API generation and cleaning

api.generate: node_modules _api.generate.protocol _api.generate.rpcmanager
api.clean: _api.clean.protocol _api.clean.rpcmanager

pbjs := ./node_modules/.bin/pbjs
pbts := ./node_modules/.bin/pbts

.PHONY: api.generate api.clean

### protocoltypes
# - API - protocoltypes

_api.generate.protocol: src/api/protocoltypes.pb.js \
src/api/protocoltypes.pb.d.ts \
Expand All @@ -45,15 +74,15 @@ _api.clean.protocol:

api/protocoltypes.proto: buf.yaml
mkdir -p $(dir $@)
buf export buf.build/berty/weshnet:$(PROTO_COMMIT_HASH) --output $(dir $@)
buf export buf.build/berty/weshnet:$(PROTOCOLTYPES_COMMIT_HASH) --output $(dir $@)
src/api/protocoltypes.pb.js: api/protocoltypes.proto
$(pbjs) -t json-module -w es6 -o $@ $<
src/api/protocoltypes.pb.d.ts: api/protocoltypes.proto
$(pbjs) -t static-module $< | $(pbts) -o $@ -
src/weshnet.types.gen.ts: api/protocoltypes.proto gen-clients.js
node gen-clients.js > $@

### rpcmanager
# - API - rpcmanager

_api.generate.rpcmanager: api/rpcmanager.proto src/api/rpcmanager.pb.js src/api/rpcmanager.pb.d.ts
_api.clean.rpcmanager:
Expand All @@ -62,41 +91,57 @@ _api.clean.rpcmanager:
api/rpcmanager.proto: go.sum go.mod
go run github.com/gfanton/grpcutil/rpcmanager/protofile > $@
src/api/rpcmanager.pb.js: api/rpcmanager.proto
$(pbjs) -t json-module -w es6 -o $@ $<
$(pbjs) -t json-module -w commonjs -o $@ $<
src/api/rpcmanager.pb.d.ts: api/rpcmanager.proto
$(pbjs) -t static-module $< | $(pbts) -o $@ -

.PHONY: api.generate _api.generate.rpcmanager _api.generate.protocol

## go bind
# - Bind : Handle gomobile bind

bind.init: $(TMPDIR)/.tool-versions .cache/bind/gomobile
.cache/bind/gomobile: go.sum go.mod
# - Bind - initialization
bind_init_files := $(TMPDIR)/.tool-versions $(gobind) $(gomobile)

$(gobind): go.sum go.mod
@mkdir -p $(dir $@)
go build -o $@ golang.org/x/mobile/cmd/gobind && chmod +x $@

$(gomobile): $(gobind) go.sum go.mod
@mkdir -p $(dir $@)
go build -o $@ golang.org/x/mobile/cmd/gomobile && chmod +x $@
$@ init
# FIXME(gfanton): find a more elegant way to make asdf works in the tmp directory
$(gomobile) init || (rm -f $@ && exit 1) # in case of failure, remove gomobile so we can init again

$(TMPDIR)/.tool-versions: .tool-versions
@echo "> copying current `.tool-versions` in '$(TMPDIR)' folder in order to make asdf works"
@echo "> copying current '.tool-versions' in '$(TMPDIR)' folder in order to make asdf works"
@echo "> this hack is needed in order for gomobile (who is building from '$(TMPDIR)') bind to use the correct javac and go version"
@cp -v $< $@
bind.clean: _bind.clean.framework
rm -f $(TMPDIR)/.tool-versions
rm -rf .cache/bind

# use `nowatchdog` tags to build, see https://github.com/libp2p/go-libp2p-connmgr/issues/98
go_files := $(shell find . -iname '*.go')
go_deps := go.mod go.sum $(go_files)
$(OUTPUT_FRAMEWORK): bind.init $(go_deps)
.PHONY: bind.init

# - Bind - ios framework

$(weshnetcore_xcframework): $(bind_init_files) $(go_deps)
@mkdir -p $(dir $@)
.cache/bind/gomobile bind -v \
# need to use `nowatchdog` tags, see https://github.com/libp2p/go-libp2p-connmgr/issues/98
$(gomobile) bind -v \
-tags 'nowatchdog' -prefix=Weshnet \
-o $@ -target ios $(make_dir)/framework/core
_bind.clean.framework:
rm -rf $(OUTPUT_FRAMEWORK)
-o $@ -target ios ./framework/core
_bind.clean.ios:
rm -rf weshnetcore_xcframework

output/android/core.aar: bind.init $(go_deps)
@mkdir -p $(dir $@)
.cache/bind/gomobile bind -v -o $@ -target android $(make_dir)/framework/core
# - Bind - android aar and jar

$(weshnetcore_jar): $(ANDROID_OUTPUT_LIBS_DIR)/WeshnetCore.aar
$(weshnetcore_aar): $(bind_init_files) $(go_deps)
@mkdir -p $(dir $@) .cache/bind/android
$(gomobile) bind -v \
-javapkg=network.weshnet \
-o $@ -target android -androidapi 21 ./framework/core
_bind.clean.android:
rm -rf $(weshnetcore_jar) $(weshnetcore_aar)


# - Bind - cleaning

include makefiles/asdf.mk
bind.clean: _bind.clean.ios _bind.clean.android
rm -f $(bind_init_files)
12 changes: 12 additions & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ apply plugin: 'maven-publish'
group = 'expo.modules.weshnetexpo'
version = '0.1.0'

// Define the custom task to run the make build command
task makeBuild(type: Exec) {
workingDir '..'
commandLine 'make', 'build.android'
}

// Make the build depend on the makeBuild task
tasks.named('preBuild').configure {
dependsOn(makeBuild)
}

buildscript {
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
if (expoModulesCorePlugin.exists()) {
Expand Down Expand Up @@ -84,6 +95,7 @@ repositories {
}

dependencies {
implementation fileTree(dir: 'libs', include: ['*.aar', '*.jar'], exclude: [])
implementation project(':expo-modules-core')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
}
41 changes: 41 additions & 0 deletions android/src/main/java/expo/modules/weshnetexpo/PromiseBlock.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package expo.modules.weshnetexpo

//
// expo.modules.weshnetexpo.PromiseBlock.kt
// WeshnetExpo
//
// Created by Guilhem Fanton on 10/07/2023.
//

import expo.modules.kotlin.Promise
import network.weshnet.core.PromiseBlock as IPromiseBlock

class PromiseBlock(val promise: Promise): IPromiseBlock {
// expo.modules.weshnetexpo.PromiseBlock aims to keep reference over promise object so go can play with
// until the promise is resolved
companion object {
private var promises = mutableSetOf<PromiseBlock>()
}

init {
store()
}

override fun callResolve(reply: String?) {
this.promise.resolve(reply ?: "")
this.remove() // cleanup the promise
}

override fun callReject(err: Exception?) {
this.promise.reject(WeshnetCoreError(err))
this.remove() // cleanup the promise
}

private fun store() {
promises.add(this)
}

private fun remove() {
promises.remove(this)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package expo.modules.weshnetexpo

import expo.modules.kotlin.exception.CodedException

class WeshnetNotStartedError : CodedException("NotStarted", "Service hasn't started yet", null)
class WeshnetCoreError(err: Exception?) : CodedException("CoreError", err)

77 changes: 43 additions & 34 deletions android/src/main/java/expo/modules/weshnetexpo/WeshnetExpoModule.kt
Original file line number Diff line number Diff line change
@@ -1,47 +1,56 @@
package expo.modules.weshnetexpo

import expo.modules.kotlin.Promise
import expo.modules.kotlin.exception.CodedException
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
import network.weshnet.core.Service
import network.weshnet.core.Core

class WeshnetExpoModule : Module() {
// Each module class must implement the definition function. The definition consists of components
// that describes the module's functionality and behavior.
// See https://docs.expo.dev/modules/module-api for more details about available components.
override fun definition() = ModuleDefinition {
// Sets the name of the module that JavaScript code will use to refer to the module. Takes a string as an argument.
// Can be inferred from module's class name, but it's recommended to set it explicitly for clarity.
// The module will be accessible from `requireNativeModule('WeshnetExpo')` in JavaScript.
Name("WeshnetExpo")
private var service: Service? = null

// Sets constant properties on the module. Can take a dictionary or a closure that returns a dictionary.
Constants(
"PI" to Math.PI
)
// Each module class must implement the definition function. The definition consists of components
// that describes the module's functionality and behavior.
// See https://docs.expo.dev/modules/module-api for more details about available components.
override fun definition() = ModuleDefinition {
Name("WeshnetExpo")

// Defines event names that the module can send to JavaScript.
Events("onChange")
AsyncFunction("init") { promise: Promise ->
try {
if (service == null) {
service = initializeCoreService()
}
promise.resolve("")
} catch (err: CodedException) {
promise.reject(err)
}
}

// Defines a JavaScript synchronous function that runs the native code on the JavaScript thread.
Function("hello") {
"Hello world! 👋"
// Defines a JavaScript function that always returns a Promise and whose native code
// is by default dispatched on a different thread than the JavaScript runtime runs on.
AsyncFunction("invokeMethod") { method: String, b64message: String, promise: Promise ->
try {
service?.let {
val block = PromiseBlock(promise)
it.invokeBridgeMethodWithPromiseBlock(block, method, b64message)
} ?: run {
throw WeshnetNotStartedError()
}
} catch (err: CodedException) {
promise.reject(err)
}
}
}

// Defines a JavaScript function that always returns a Promise and whose native code
// is by default dispatched on the different thread than the JavaScript runtime runs on.
AsyncFunction("setValueAsync") { value: String ->
// Send an event to JavaScript.
sendEvent("onChange", mapOf(
"value" to value
))
@Throws(CodedException::class)
private fun initializeCoreService(): Service {
// Add logic to create and return your service instance
// If an error occurs, throw WeshnetError2.kt
try {
return Core.newService()
} catch (err: Exception) {
throw WeshnetCoreError(err)
}
}

// Enables the module to be used as a native view. Definition components that are accepted as part of
// the view definition: Prop, Events.
View(WeshnetExpoView::class) {
// Defines a setter for the `name` prop.
Prop("name") { view: WeshnetExpoView, prop: String ->
println(prop)
}
}
}
}
1 change: 1 addition & 0 deletions example/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ buildscript {
dependencies {
classpath('com.android.tools.build:gradle:7.4.1')
classpath('com.facebook.react:react-native-gradle-plugin')
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.0'
}
}

Expand Down
1 change: 1 addition & 0 deletions example/android/settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ applyNativeModulesSettingsGradle(settings)

include ':app'
includeBuild(new File(["node", "--print", "require.resolve('react-native-gradle-plugin/package.json')"].execute(null, rootDir).text.trim()).getParentFile())
include ':weshnet'
Loading

0 comments on commit 1da26d9

Please sign in to comment.