Multi-OS Engine: Create iOS Apps in Java (or Kotlin ... etc.)

Overview

Multi-OS Engine

Overview

Multi-OS Engine provides a Java runtime and Java interfaces to iOS platform API to develop native iOS applications with native look and feel, native performance, and portability of common Java logic modules from your Android Apps. It comes fully integrated with Android Studio hosted on macOS or Windows and contains all the development tools needed to develop an iOS app and publish to the App Store.

Getting Started

Building from Source Code

Requirements

  • Apple macOS 10.14+
  • Minimum 8GB RAM

Install the 'repo' Tool

mkdir ~/bin
PATH=~/bin:$PATH
curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
chmod a+x ~/bin/repo

You may also install the repo using brew:

brew install repo

Get the Source Code

Mainline branch:

repo init -u https://github.com/multi-os-engine/manifest.git -b moe-master
repo sync

Note: if you want to get the source code from development branch with initial support of Windows and bitcode, switch to "moe-windows-bitcode" branch:

repo init -u https://github.com/multi-os-engine/manifest.git -b moe-windows-bitcode
repo sync

Installing Homebrew & Dependencies

Install brew from brew.sh, then you can install MOE's dependencies:

brew tap homebrew/versions
brew install autogen autoconf automake libtool pkg-config wget isl cloog cmake gpg ant maven mpfr libmpc 

cd <repo>/moe/moe-core
brew install file://`pwd`/dependencies/premake5.rb

Building MinGW & LLVM

Note: On MacOS Mojave the header files are not installed automatically. To install them, execute the following commands:

sudo rm -rf /Library/Developer/CommandLineTools # We remove the previously installed command line tools to make sure we have the latest one
xcode-select --install   # Install the latest one
open /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg # Install the headers
sudo xcode-select --reset # To make sure that the default toolchain path points to Xcode (required by xcodebuild)

Building the complete SDK and related tools requires LLVM and MinGW. To build these execute the following:

cd <repo>/prebuilts
./gradlew mingw llvm

This step only needs to be done once (or until MinGW or LLVM components/requirements are changed).

Building Multi-OS Engine Core

The moe/moe-core repo contains the runtime (and some compile time) components of MOE. To build the frameworks and build tools, execute the following:

cd <repo>/moe/moe-core
./gradlew build

Building Multi-OS Engine Tools

The moe/tools repositories contain the tools required to build and run MOE applications.

SDK Publisher: creating a developer SDK:

cd <repo>/moe/tools/master
./gradlew :moe-sdk:devsdk

Gradle Plugin: building and publishing the Gradle plugin to Maven local:

cd <repo>/moe/tools/master
./gradlew :moe-gradle:publishToMavenLocal

Maven Plugin: building and publishing the Maven plugin to Maven local:

cd <repo>/moe/tools/moe.plugin.maven
mvn clean install

IDEA Plugin: building the IDEA plugin:

cd <repo>/moe/tools/master
./gradlew :moe.plugin.idea:build

Eclipse Plugin: building the Eclipse plugin:

cd <repo>/moe/tools/moe.plugin.eclipse
mvn clean install -DBUILD_NUMBER=1
Comments
  • OutOfMemoryError when building with new (1.1) MOE version

    OutOfMemoryError when building with new (1.1) MOE version

    After I upgraded from 1.0.710 to 1.1+ (uninstalled the old one first with the uninstall app, then installed the new IntelliJ plugin, updated my build.gradle like in a new template project, changed all com.intel.moe imports to org.moe), I can't build our MOE project anymore, I'm getting an OutOfMemory exception after about a minute or two:

    UNEXPECTED TOP-LEVEL ERROR:
    java.lang.OutOfMemoryError: Java heap space: failed reallocation of scalar replaced objects
        at java.util.HashSet.<init>(HashSet.java:105)
        at com.android.dx.ssa.Dominators.compress(Dominators.java:121)
        at com.android.dx.ssa.Dominators.eval(Dominators.java:160)
        at com.android.dx.ssa.Dominators.run(Dominators.java:207)
        at com.android.dx.ssa.Dominators.make(Dominators.java:90)
        at com.android.dx.ssa.DomFront.run(DomFront.java:86)
        at com.android.dx.ssa.SsaConverter.placePhiFunctions(SsaConverter.java:297)
        at com.android.dx.ssa.SsaConverter.convertToSsaMethod(SsaConverter.java:51)
        at com.android.dx.ssa.Optimizer.optimize(Optimizer.java:98)
        at com.android.dx.ssa.Optimizer.optimize(Optimizer.java:72)
        at com.android.dx.dex.cf.CfTranslator.processMethods(CfTranslator.java:297)
        at com.android.dx.dex.cf.CfTranslator.translate0(CfTranslator.java:137)
        at com.android.dx.dex.cf.CfTranslator.translate(CfTranslator.java:93)
        at com.android.dx.command.dexer.Main.processClass(Main.java:729)
        at com.android.dx.command.dexer.Main.processFileBytes(Main.java:673)
        at com.android.dx.command.dexer.Main.access$300(Main.java:82)
        at com.android.dx.command.dexer.Main$1.processFileBytes(Main.java:602)
        at com.android.dx.cf.direct.ClassPathOpener.processOne(ClassPathOpener.java:170)
        at com.android.dx.cf.direct.ClassPathOpener.processDirectory(ClassPathOpener.java:229)
        at com.android.dx.cf.direct.ClassPathOpener.processOne(ClassPathOpener.java:158)
        at com.android.dx.cf.direct.ClassPathOpener.processDirectory(ClassPathOpener.java:229)
        at com.android.dx.cf.direct.ClassPathOpener.processOne(ClassPathOpener.java:158)
        at com.android.dx.cf.direct.ClassPathOpener.processDirectory(ClassPathOpener.java:229)
        at com.android.dx.cf.direct.ClassPathOpener.processOne(ClassPathOpener.java:158)
        at com.android.dx.cf.direct.ClassPathOpener.processDirectory(ClassPathOpener.java:229)
        at com.android.dx.cf.direct.ClassPathOpener.processOne(ClassPathOpener.java:158)
        at com.android.dx.cf.direct.ClassPathOpener.processDirectory(ClassPathOpener.java:229)
        at com.android.dx.cf.direct.ClassPathOpener.processOne(ClassPathOpener.java:158)
        at com.android.dx.cf.direct.ClassPathOpener.processDirectory(ClassPathOpener.java:229)
        at com.android.dx.cf.direct.ClassPathOpener.processOne(ClassPathOpener.java:158)
        at com.android.dx.cf.direct.ClassPathOpener.process(ClassPathOpener.java:144)
        at com.android.dx.command.dexer.Main.processOne(Main.java:632)
     FAILED
    
    FAILURE: Build failed with an exception.
    
    * What went wrong:
    Execution failed for task ':ios:moeMainDex'.
    > Task failed, you can find the log file here: /Users/alex/muvix/ios/build/moe/main/dex/Dex.log
    
    * Try:
    Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
    
    BUILD FAILED
    
    bug 
    opened by muvixalex 16
  • Backport Java 8 features using desugar_jdk_libs & ProGuard

    Backport Java 8 features using desugar_jdk_libs & ProGuard

    https://github.com/google/desugar_jdk_libs

    ProGuard is able to process classes under java.* package, so it migth be possible to backporting some Java 8 runtime classes to current ART runtime.

    If this is not possible, then we could just use ProGuard's own backport with net.sourceforge.streamsupport and org.threeten https://www.guardsquare.com/manual/configuration/usage#target

    feature prio/high component/java-runtime-library 
    opened by Noisyfox 13
  • detect if debug build from java without any obj-c code

    detect if debug build from java without any obj-c code

    i have been working with MOE for a few months and managed to write a whole game in java using the apple API, and i love the fact ive not had to learn any new language syntax, and ive been able to do everything i can dream of in java, everything except one thing, detect if this is a debug build, ive been told that this is really simple:

    "You could write a small function in ObjC that returns different value with "#ifdef DEBUG", then call this from your Java code."

    but considering i have never written any ObjC, have no idea where to put the file, what to put into the file, or how to then call it from java, things ive never needed to do so far, i would actually need to learn quite a lot to do this simple task.

    also swift and kotlin has "Platform.isDebugBinary" so java should have a method too!

    there are many workaround for this, but its always going to be easier to just have a nice method to call

    feature prio/high component/java-runtime-library 
    opened by sdyura 10
  • Unable to run (debug) from IntelliJ

    Unable to run (debug) from IntelliJ

    I tried on iPhone 5s with iOS 9.3.4 and on iPhone 4s with iOS 8.4 and I get the same error:

    ERROR: Failed to get installation proxy (instproxy_client_start_service returned -256)

    Like described also here:

    http://stackoverflow.com/questions/38807063/error-failed-to-get-installation-proxy-instproxy-client-start-service-returned

    opened by alexitelman 9
  • Used frameworks do not loaded automatically on iOS simulator

    Used frameworks do not loaded automatically on iOS simulator

    when i try and run this method i get an exception

    /Users/noisyfox/Documents/project/moe/moe/natj/natj/src/main/native/natj/ObjCRuntime.mm:1480 WARNING: Binding class refers to class MFMailComposeViewController, but it can not be found. Fallback to indirect super class. 2021-12-22 23:03:57.994 Domination[20487:707237] +[UINavigationController canSendMail]: unrecognized selector sent to class 0x7fff864ac6b8

    this method works fine on an actual device, but its annoying that it cant just return false on the simulator

    bug component/natj prio/high 
    opened by sdyura 5
  • Change System.loadLibrary to match the standard Java behaviour (throw UnsatisfiedLinkError when necessary)

    Change System.loadLibrary to match the standard Java behaviour (throw UnsatisfiedLinkError when necessary)

    As defined in the official javadocs, System.loadLibrary should throw UnsatisfiedLinkError if the avalable library is unavailable. However, when using MOE 1.2.4, calling System.loadLibrary with an invalid library name simply returns as if the library was successfully loaded.

    This can break code which attempts to load a native implementation, and falls back to a pure-Java implementation if the native library can't be found, as since no exception is thrown it will attempt to use natively defined methods which aren't available, causing a crash at the call site during runtime.

    feature 
    opened by kevin-vf 5
  • MOE-1.x: Use proguard for backporting instead of retrolambda

    MOE-1.x: Use proguard for backporting instead of retrolambda

    The reason of doing this is to support backporting Java 8 stream and date API using the net.sourceforge.streamsupport and org.threeten lib. See the -target option in proguard manual for more details.

    Currently in MOE 1.x retrolambda is used for not only backporting the lambda, but also to support default methods in interfaces, and also to inject MOE annotations and class registration code to binding classes. If the backporting is done by proguard, then we need to replace the retrolambda with another tool to perform those MOE specific operations. This can be done by the moe-tools-classvalidator tool from MOE-2.x branch, with SVM related code removed.

    feature component/gradle-plugin component/build-system 
    opened by Noisyfox 4
  • Runtime issue using jackson-databind

    Runtime issue using jackson-databind

    I've run into a strange issue trying to use jackson in a MOE project. When using a simple JsonProperty annotated kotlin class, jackson will fail at runtime with a null pointer exception. However, any attempt to place a breakpoint in the function where the exception occurs causes the error to not occur. If you run the debugger with no breakpoints, it will fail just as if running the application normally.

    Note that this is the most basic use of jackson possible (serializing a simple object with a single property), and it does work both on desktop platforms and android. This is just the simplest way to reproduce the bug I encountered while testing MOE with a larger application. So I assume this is not (directly) a jackson bug.

    I'm using IDEA 2016.1 with the 1.2.1 MOE plugin, and the project is using MOE 1.2.4, tested on an iPad running iOS 9.1. Using the moeLaunch gradle task directly doesn't change the outcome (unsurprisingly since the IDE runs the task anyways).

    I've pushed the example code to reproduce the bug at https://github.com/kevin-vf/moe-jackson-bug This is the just the generated single view project code (converted to kotlin), with the code to trigger the bug in Main::applicationDidFinishLaunchingWithOptions.

    I put a custom proguard.cfg to rule out proguard from somehow being the cause by keeping all possible classes, but it doesn't change the outcome. You can remove it and put some reference to the installationId field in the code or a proguard rule to avoid it being removed and the error will still occur.

    bug prio/high 
    opened by kevin-vf 4
  • Retrofit with OkHttp crashes with SIGPIPE when returning from lock screen

    Retrofit with OkHttp crashes with SIGPIPE when returning from lock screen

    When using Retrofit with OkHttp, when locking the screen on a real device (iPhone 5s, iOS 9.3.4) with the power button, and then unlocking the screen, the app sometimes (often) crashes with SIGPIPE.

    I'm working on a repo case, in the meantime, did you encounter this before? Do you have a workaround?

    Thanks, Alex.

    opened by muvixalex 4
  • MOE Plugin crashing Android Studio

    MOE Plugin crashing Android Studio

    I'm using OS X El capitan. All steps to install finish correctly after execution of moe_install.sh. Then when i try to use Android Studio the module crashes and Androis Studio restarts. And i'm not able to use MOE.

    My environment specs are:

    • OS X El Capitan (10.11.6)
    • Android Studio 2.1.3
    • JRE/JDK 1.8
    opened by angyay0 4
  • clangFirstWord() fails when property has a UpperCase letter at beginning.

    clangFirstWord() fails when property has a UpperCase letter at beginning.

    I tried to generate bindings for a header that contained this (from a swift pod):

    /// The current visible drop down. There can be only one visible drop down at a time.
    SWIFT_CLASS_PROPERTY(@property (nonatomic, class, weak) DropDown * _Nullable VisibleDropDown;)
    + (DropDown * _Nullable)VisibleDropDown SWIFT_WARN_UNUSED_RESULT;
    + (void)setVisibleDropDown:(DropDown * _Nullable)value;
    

    This fails with a StringIndexOutOfBoundsException. The cause is in the "clangFirstWord" method:

            while (loc < selector.length()) {
                char c = selector.charAt(loc);
                if (Character.isUpperCase(c) || c == ':') break; // This loop will directly break here, because the first letter is upper case. loc 
                                                                                         // will still be zero
                ++loc;
            }
    

    Then it will go into this if statement:

            if (loc == 0) {
                while (loc < selector.length()) {
                    char c = selector.charAt(loc);
                    if (Character.isLowerCase(c) || c == ':') break; // It will break here when loc = 1.
                    ++loc;
                }
                if (loc != selector.length()) --loc; // After the loop is loc equals 1. But here it will get subtracted by 1. So loc is again zero.
            }
    

    So the resulting substring return selector.substring(start, loc); will be empty. This will result in these lines: https://github.com/multi-os-engine/moe-natjgen/blob/moe-master/src/main/java/org/moe/natjgen/ObjCMethod.java#L497-L498 in the StringIndexOutOfBoundsException.

    My idea was to to implement a check in the first loop, that when the character is upper case, the loop only breaks if loc != 0. Maybe also make the first letter lower case (only for the java method name)? But I don't know enough about ObjC and Swift naming conventions etc. to know, whether this would break anything.

    bug 
    opened by Berstanio 3
  • crash in MOE 1.9.0 GC: isMarked -> Test -> LogMessage -> Abort

    crash in MOE 1.9.0 GC: isMarked -> Test -> LogMessage -> Abort

    here are some example crash logs

    2022-12-24_00-22-35.9412_+0100-a98564f8a6e08c08359d6ea5a22c8b5a438053d6.txt 2022-12-25_13-35-04.2547_+0100-07b8cedaf4f41d5e9edf9330ae253898b0305813.txt 2022-12-26_00-43-01.1471_+0100-51f3cce7e8fc1401f6a3e04405bf6875a1ef9787.txt 2022-12-26_01-33-57.9709_+0100-71918938fef512db9837acf87d1ec9d234e7a33e.txt

    in heap_bitmap-inl.h

    inline bool HeapBitmap::Test(const mirror::Object* obj) {
      ContinuousSpaceBitmap* bitmap = GetContinuousSpaceBitmap(obj);
      if (LIKELY(bitmap != nullptr)) {
        return bitmap->Test(obj);
      }
      for (const auto& lo_bitmap : large_object_bitmaps_) {
        if (LIKELY(lo_bitmap->HasAddress(obj))) {
          return lo_bitmap->Test(obj);
        }
      }
      LOG(FATAL) << "Invalid object " << obj;
      return false;
    }
    

    this may be a bug in android that may have been fixed already in android

    opened by sdyura 0
  • [Build System] moe-core does not always generate icugen *.spp files

    [Build System] moe-core does not always generate icugen *.spp files

    I ran into the issue, when doing MOE ci builds, that sometimes doing "./gradlew build" does not always ensure, that the icu *.spp files get generated. Running "./gradlew build" again fixes this issue. However, especially for ci, it would be good to have a reliable "build" task-

    opened by Berstanio 0
  • 32bit is unsupported from Xcode 14+

    32bit is unsupported from Xcode 14+

    https://developer.apple.com/documentation/xcode-release-notes/xcode-14-release-notes "Building iOS projects with deployment targets for the armv7, armv7s, and i386 architectures is no longer supported. (92831716)"

    https://github.com/multi-os-engine/moe-generator-project/blob/a652a00969956b0691d8aadb7712b7f508a553d7/src/main/resources/org/moe/generator/project/moe.build.script.sh.in#L73

    The main problem is that "lipo -thin" fails for the iphoneos sdk, because it isn't a fat file anymore. Maybe we can just delete the thining, since iossimulator thining doesn't matter?

    opened by Berstanio 0
  • Android Studio generated project disallows project repos by default

    Android Studio generated project disallows project repos by default

    In AS Dolphin 2021.3.1 android studio generates a project with the following settings.gradle:

    pluginManagement {
        repositories {
            mavenLocal()
            gradlePluginPortal()
            google()
            mavenCentral()
        }
    }
    dependencyResolutionManagement {
        repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
        repositories {
            mavenLocal()
            google()
            mavenCentral()
        }
    }
    rootProject.name = "My Application"
    include ':app'
    

    The problem is, that MOE injects repos into the project with the gradle plugin, which leads to a failure because of this configuration repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS). So this should be either explain this in the docs or fixed on MOE side.

    opened by Berstanio 0
  • Print warning if NatJ creates a binding object based on a super class

    Print warning if NatJ creates a binding object based on a super class

    I had the following issue: I had a abstract super class A with subclass B and subclass C. Than I had a block callback that got a object of type A as a parameter. But type A was never actually passed, only either B or C. Than in the callback I had something like:

    if (paramA instanceof B) {
         // Do x
    } else if (paramA instanceof C) {
        // Do y
    }
    

    The problem was now, that I have never referenced class B or C directly, so their static init block with NatJ.register() was never executed. When now the callback was called NatJ created a binding java object A for the ObjC classes B and C, so my "instanceof" code wasn't working correctly. It would be probably good to have NatJ print a warning in such cases, something like "No binding class for ObjC class B/C was registered, falling back to superclass binding A".

    opened by Berstanio 0
  • Discussion: What should

    Discussion: What should "java.vm.name" report

    Currently the "java.vm.name" property reports "Dalvik", as android does. https://github.com/multi-os-engine/libcore/blob/eae58129e020f95e9b4a506cea4cc90cc21b09a3/luni/src/main/java/java/lang/System.java#L769 This means, some third party libs that have android specific code pathes, will recognize MOE as android, e.g. okhttp3/retrofit.

    This can be working, if the code path is just about libcore specific implementations. However, if the code path relies of something android os specific, the code path will fail.

    What should we do about it? Don't report "Dalvik"? Make it configurable?

    opened by Berstanio 0
Owner
Multi-OS Engine
Create iOS and cross-platform apps in Java
Multi-OS Engine
Cross-platform framework for building truly native mobile apps with Java or Kotlin

Cross-platform framework for building truly native mobile apps with Java or Kotlin. Write Once Run Anywhere support for iOS, Android, Desktop & Web.

Codename One 1.4k Jan 5, 2023
Small app to create icon sets for Linux, Windows, OSX, Android and IOS from a single PNG image

FXIconcreator Small app to create icon sets (multi resolution) for Linux, Windows, OSX from a single PNG image Reason for creating such an app was tha

null 18 Aug 4, 2022
Drools is a rule engine, DMN engine and complex event processing (CEP) engine for Java.

An open source rule engine, DMN engine and complex event processing (CEP) engine for Java™ and the JVM Platform. Drools is a business rule management

KIE (Drools, OptaPlanner and jBPM) 4.9k Dec 31, 2022
There are two challenges one is to create a backend api the other is to create a frontend application to consume the public data api devall.

Sobre | Desafio | Resolução | Tecnologias | Execução | Itexto desafio tecnico Sobre os Desafios existem dois desafios um é criar uma api backend o out

fabricio S Miranda 1 Oct 18, 2021
Rivr is a lightweight open-source dialogue engine enabling Java developers to easily create enterprise-grade VoiceXML applications.

Overview Rivr is a lightweight open-source dialogue engine enabling Java developers to easily create enterprise-grade VoiceXML applications. Read our

Nu Echo Inc. 57 Jun 27, 2022
Render After Effects animations natively on Android and iOS, Web, and React Native

Lottie for Android, iOS, React Native, Web, and Windows Lottie is a mobile library for Android and iOS that parses Adobe After Effects animations expo

Airbnb 33.5k Jan 3, 2023
Metremenqeemi - Android/iOS app to teach the Coptic Language

ⲙⲉⲧⲣⲉⲙⲛ̀ⲭⲏⲙⲓ The Open Source Android/iOS app to learn how to read and understand the Coptic Language Join our Discord Channel About the Curriculum The

Mark Yacoub 8 Aug 30, 2022
React wrapper for android and ios

Deepvue Aadhaar Offline e-KYC React Native SDK This is a wrapper over Android and iOS SDK for react native. Aadhaar Paperless Offline eKYC is a secure

null 2 May 10, 2022
Example how to reduce React Native iOS build times drastically

Reduce React Native iOS build times Introduction Amount of code in pods can be huge. Pods don’t change often. On CI, all pods are compiled over and ov

Dirk Postma 28 Dec 15, 2022
An Android library for member secretGFX group, This can be used to growing your apps and get more install via a simple banner view & native view and interstitial dialog.

GFX-AdPromote An Android library for member secretGFX group, This can be used to growing your apps and get more install via a simple banner view & nat

SAID MOTYA 10 Dec 25, 2022
psionic: packages apps realmeParts

RealmeParts RealmeParts is an open-source Android application written in java. It offers several customization settings while making use of various de

psionicprjkt 5 Feb 21, 2022
Simple AnimationUtil using Easing functions. Can be used anywhere, Hacked-Client, Mods, etc..

AnimationUtil Simple AnimationUtil using Easing functions. Can be used anywhere, Hacked-Client, Mods, etc.. Render example > https://gyazo.com/780b5d8

null 71 Jan 8, 2023
A simple app to use Xposed without root, unlock the bootloader or modify system image, etc.

中文文档 Introduction VirtualXposed is a simple App based on VirtualApp and epic that allows you to use an Xposed Module without needing to root, unlock t

VirtualXposed 14k Jan 8, 2023
Some recent questions asked in interviews of companies like Google, TCS, Amazon etc.

Some recent questions asked in interviews of companies like Google, TCS, Amazon etc.

Ashish Kumar 19 Nov 21, 2022
100+ Spring Boot Articles, Tutorials, Video tutorials, Projects, Guides, Source code examples etc

YouTube Channel - Spring Boot Tutorial Subscribe for future video and updates Spring Boot Tutorial on YouTube Newly published spring boot tutorials (2

Ramesh Fadatare 1.2k Jan 2, 2023
Tracks information (skills, inventory, bank, etc.) about a group ironman player and sends it to a website for other group members to view

Group Ironmen Tracker Plugin Website: groupiron.men Source for frontend and server: https://github.com/christoabrown/group-ironmen This plugin tracks

Christopher Brown 7 Nov 11, 2022
Send and Receive anonymous messages, opinions, confessions etc.

WSS - Wanna Say Something Send and Receive anonymous messages, opinions, confessions etc. Explore the docs » • Report Bug • Request Feature • About Th

Prasoon Soni 8 Nov 26, 2022
Tons of HUD tweaks including player stats, block/entity info, etc.

Tons of HUD tweaks including player stats, block/entity info, etc. Created because I've been driven mad by the annoying process of downloading that many mods to achieve the same goal.

Intelligent Creations 9 Dec 23, 2022
Multi-Tenant Spring Boot Application with separate databases using Hibernate and H2.

Multi-Tenant Spring Boot Application A Spring Boot application that utilises a multi-tenancy architecture by providing multiple databases, one for eac

Alex Gschnitzer 15 May 9, 2022