├── .gitattributes ├── .github └── workflows │ ├── pr.yml │ └── release.yml ├── .gitignore ├── README.md ├── build.gradle.kts ├── generator ├── README.md ├── build.gradle.kts ├── generator-cli │ ├── README.md │ ├── build.gradle.kts │ └── src │ │ ├── main │ │ ├── kotlin │ │ │ └── io │ │ │ │ └── ia │ │ │ │ └── sdk │ │ │ │ └── tools │ │ │ │ └── module │ │ │ │ └── cli │ │ │ │ └── ModuleGeneratorCli.kt │ │ └── resources │ │ │ └── resource-config.json │ │ └── test │ │ └── kotlin │ │ └── io │ │ └── ia │ │ └── sdk │ │ └── tools │ │ └── module │ │ └── cli │ │ └── ModuleGeneratorCliTest.kt ├── generator-core │ ├── README.md │ ├── build.gradle.kts │ └── src │ │ ├── integrationTest │ │ └── kotlin │ │ │ └── io │ │ │ └── ia │ │ │ └── ignition │ │ │ └── module │ │ │ └── generator │ │ │ └── IntegrationTests.kt │ │ ├── main │ │ ├── kotlin │ │ │ └── io │ │ │ │ └── ia │ │ │ │ └── ignition │ │ │ │ └── module │ │ │ │ └── generator │ │ │ │ ├── ModuleGenerator.kt │ │ │ │ ├── api │ │ │ │ ├── DefaultDependencies.kt │ │ │ │ ├── GeneratorConfig.kt │ │ │ │ ├── GeneratorConfigBuilder.kt │ │ │ │ ├── GeneratorContext.kt │ │ │ │ ├── GradleDsl.kt │ │ │ │ ├── ProjectScope.kt │ │ │ │ ├── SourceFileType.kt │ │ │ │ ├── TemplateMarker.kt │ │ │ │ └── buildFiles.kt │ │ │ │ ├── data │ │ │ │ ├── ModuleGeneratorContext.kt │ │ │ │ ├── ValidationResult.kt │ │ │ │ └── validation.kt │ │ │ │ ├── error │ │ │ │ └── IllegalConfigurationException.kt │ │ │ │ └── util │ │ │ │ ├── extensions.kt │ │ │ │ └── generatorUtils.kt │ │ └── resources │ │ │ ├── gradle │ │ │ └── wrapper │ │ │ │ ├── 6_4_1 │ │ │ │ ├── gradle-wrapper.jar │ │ │ │ ├── gradle-wrapper.properties │ │ │ │ └── scripts │ │ │ │ │ ├── gradlew │ │ │ │ │ └── gradlew.bat │ │ │ │ ├── 6_5_1 │ │ │ │ ├── gradle-wrapper.jar │ │ │ │ ├── gradle-wrapper.properties │ │ │ │ └── scripts │ │ │ │ │ ├── gradlew │ │ │ │ │ └── gradlew.bat │ │ │ │ ├── 6_8_2 │ │ │ │ ├── gradle-wrapper.jar │ │ │ │ ├── gradle-wrapper.properties │ │ │ │ └── scripts │ │ │ │ │ ├── gradlew │ │ │ │ │ └── gradlew.bat │ │ │ │ ├── 7_2 │ │ │ │ ├── gradle-wrapper.jar │ │ │ │ ├── gradle-wrapper.properties │ │ │ │ └── scripts │ │ │ │ │ ├── gradlew │ │ │ │ │ └── gradlew.bat │ │ │ │ ├── 7_5_1 │ │ │ │ ├── gradle-wrapper.jar │ │ │ │ ├── gradle-wrapper.properties │ │ │ │ └── scripts │ │ │ │ │ ├── gradlew │ │ │ │ │ └── gradlew.bat │ │ │ │ └── 7_6 │ │ │ │ ├── gradle-wrapper.jar │ │ │ │ ├── gradle-wrapper.properties │ │ │ │ └── scripts │ │ │ │ ├── gradlew │ │ │ │ └── gradlew.bat │ │ │ └── templates │ │ │ ├── buildscript │ │ │ ├── client.build.gradle │ │ │ ├── client.build.gradle.kts │ │ │ ├── common.build.gradle │ │ │ ├── common.build.gradle.kts │ │ │ ├── designer.build.gradle │ │ │ ├── designer.build.gradle.kts │ │ │ ├── gateway.build.gradle │ │ │ ├── gateway.build.gradle.kts │ │ │ ├── root.build.gradle │ │ │ └── root.build.gradle.kts │ │ │ ├── config │ │ │ ├── modlPluginConfig.groovy │ │ │ └── modlPluginConfig.kts │ │ │ ├── hook │ │ │ ├── ClientHook.java │ │ │ ├── ClientHook.kt │ │ │ ├── DesignerHook.java │ │ │ ├── DesignerHook.kt │ │ │ ├── GatewayHook.java │ │ │ ├── GatewayHook.kt │ │ │ ├── Module.java │ │ │ └── Module.kt │ │ │ ├── settings.gradle │ │ │ ├── settings.gradle.kts │ │ │ └── version-catalog │ │ │ └── libs.versions.toml │ │ └── test │ │ ├── java │ │ └── io │ │ │ └── ia │ │ │ └── ignition │ │ │ └── module │ │ │ └── generator │ │ │ └── ModuleGeneratorJavaTest.java │ │ ├── kotlin │ │ └── io │ │ │ └── ia │ │ │ └── ignition │ │ │ └── module │ │ │ └── generator │ │ │ ├── ModuleGeneratorTest.kt │ │ │ ├── api │ │ │ └── ProjectScopeTest.kt │ │ │ ├── data │ │ │ ├── GeneratorConfigTest.kt │ │ │ ├── GeneratorContextTest.kt │ │ │ └── ValidationTest.kt │ │ │ └── util │ │ │ ├── ExtensionTests.kt │ │ │ └── GeneratorUtilsTest.kt │ │ └── resources │ │ ├── appendedResource.txt │ │ ├── deeper │ │ └── deeperResource.txt │ │ └── fillFromResourceTest.txt ├── gradle │ ├── libs.versions.toml │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts ├── gradle-module-plugin ├── Contributing.md ├── README.md ├── SIGNING_VIA_HSM.md ├── build.gradle.kts ├── gradle.properties ├── gradle │ ├── libs.versions.toml │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts └── src │ ├── functionalTest │ ├── kotlin │ │ └── io │ │ │ └── ia │ │ │ └── sdk │ │ │ └── gradle │ │ │ └── modl │ │ │ ├── BaseTest.kt │ │ │ ├── IgnitionModulePluginFunctionalTest.kt │ │ │ ├── task │ │ │ ├── AssembleModuleStructureTest.kt │ │ │ ├── CollectModlDependenciesTest.kt │ │ │ ├── ModuleBuildReportTest.kt │ │ │ ├── SignModuleTest.kt │ │ │ ├── WriteModuleXmlTest.kt │ │ │ └── ZipModuleTests.kt │ │ │ └── util │ │ │ └── utils.kt │ └── resources │ │ ├── certs │ │ ├── README.md │ │ ├── certificate.cer │ │ ├── certificate.pem │ │ ├── keystore.jks │ │ ├── pkcs11-yk5-win.cfg │ │ ├── pkcs11-yk5-win.crt │ │ └── pkcs11.cfg │ │ ├── common │ │ └── common.build.gradle │ │ ├── designer │ │ ├── DesignerHook.java │ │ └── designer.build.gradle │ │ ├── docs │ │ ├── multipleFiles │ │ │ ├── index.html │ │ │ └── linked.html │ │ └── singleFile │ │ │ └── index.html │ │ ├── gateway │ │ ├── GatewayHook.java │ │ └── gateway.build.gradle │ │ └── java │ │ └── NotRealClass1.java │ ├── main │ └── kotlin │ │ └── io │ │ └── ia │ │ └── sdk │ │ └── gradle │ │ └── modl │ │ ├── IgnitionModlPlugin.kt │ │ ├── api │ │ ├── Constants.kt │ │ └── ModuleConfigException.kt │ │ ├── extension │ │ ├── ModuleDependencySpec.kt │ │ └── ModuleSettings.kt │ │ ├── model │ │ ├── Models.kt │ │ └── manifests.kt │ │ ├── task │ │ ├── AssembleModuleStructure.kt │ │ ├── Checksum.kt │ │ ├── CollectModlDependencies.kt │ │ ├── Deploy.kt │ │ ├── ModuleBuildReport.kt │ │ ├── SignModule.kt │ │ ├── WriteModuleXml.kt │ │ └── ZipModule.kt │ │ └── util │ │ └── utilities.kt │ └── test │ └── kotlin │ └── io │ └── ia │ └── sdk │ └── gradle │ └── modl │ └── IgnitionModlPluginTest.kt ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts /.gitattributes: -------------------------------------------------------------------------------- 1 | # 2 | # https://help.github.com/articles/dealing-with-line-endings/ 3 | # 4 | # These are explicitly windows files and should use crlf 5 | *.bat text eol=crlf 6 | 7 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Gradle 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 3 | 4 | name: Pull Request 5 | 6 | on: [pull_request] 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Set up JDK 11 16 | uses: actions/setup-java@v1 17 | with: 18 | java-version: '11.0.11' 19 | architecture: x64 20 | - name: Grant execute permission for generator gradlew 21 | working-directory: ./generator 22 | run: chmod +x gradlew 23 | - name: Gradle Generator Build Task 24 | working-directory: ./generator 25 | run: ./gradlew build --stacktrace --info 26 | - name: Grant execute permission for module plugin gradlew 27 | working-directory: ./gradle-module-plugin 28 | run: chmod +x gradlew 29 | - name: Gradle Module Plugin Build Task 30 | working-directory: ./gradle-module-plugin 31 | run: ./gradlew build --stacktrace --info 32 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # This workflow executes upon an merge into master, resulting in a build and (upon success) the publication of a new 2 | # release based on the merge. 3 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 4 | 5 | name: Release 6 | # if: github.event_name == 'pull_request' && github.event.action == 'closed' && github.event.pull_request.merged == true 7 | 8 | on: 9 | push: 10 | branches: 11 | - master 12 | 13 | jobs: 14 | build: 15 | 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Set up JDK 11 21 | uses: actions/setup-java@v1 22 | with: 23 | java-version: '11.0.11' 24 | architecture: x64 25 | - name: Grant execute permission for generator gradlew 26 | working-directory: ./generator 27 | run: chmod +x gradlew 28 | - name: Gradle Generator Build Task 29 | working-directory: ./generator 30 | run: ./gradlew build --stacktrace --info 31 | - name: Grant execute permission for module plugin gradlew 32 | working-directory: ./gradle-module-plugin 33 | run: chmod +x gradlew 34 | - name: Gradle Module Plugin Build Task 35 | working-directory: ./gradle-module-plugin 36 | run: ./gradlew build --stacktrace --info 37 | - name: Publish Plugins 38 | working-directory: ./gradle-module-plugin 39 | run: ./gradlew publishPlugins -Pgradle.publish.key=${{secrets.PLUGIN_PUBLISHING_KEY}} -Pgradle.publish.secret=${{secrets.PLUGIN_PUBLISHING_SECRET}} 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore Gradle project-specific cache directory 2 | .gradle 3 | 4 | # Ignore Gradle build output directory 5 | build/ 6 | 7 | # Intellij / IDE files 8 | .idea/ 9 | out/ 10 | 11 | gradle-module-plugin/.idea/ 12 | gradle-module-plugin/.gradle/ 13 | gradle-module-plugin/out/ 14 | gradle-module-plugin/build/ 15 | 16 | generator-core/.idea/ 17 | generator-core/.gradle/ 18 | generator-core/out/ 19 | generator-core/build/ 20 | 21 | generator-cli/.idea/ 22 | generator-cli/.gradle/ 23 | generator-cli/out/ 24 | generator-cli/build/ 25 | 26 | gh_key 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ignition Module Tools 2 | 3 | This repository holds a number of tools that are used to create and/or support the creation of modules intended for Inductive Automation's Ignition platform. 4 | 5 | ## Contents 6 | 7 | Contains three subprojects. See the linked readmes for details 8 | 9 | 1. A plugin for the [Gradle](https://www.gradle.org) build system which provides the ability to assemble valid Ignition modules. 10 | 11 | 1. [Ignition Module Generator Core](https://github.com/inductiveautomation/ignition-module-tools/tree/master/generator/generator-core#readme) library for creating Ignition module skeletons. Written in Kotlin, usable in Kotlin, Java and other JVM languages via jar-based dependency available as Maven artifacts. 12 | 13 | 2. [Module Generator CLI](https://github.com/inductiveautomation/ignition-module-tools/tree/master/generator/generator-cli#readme) command line application which uses the generator library to provide a _command line interface_ (CLI) that can be used to create modules from your operating system's terminal/console. 14 | 15 | 16 | #### See the README.md in the appropriate subproject for more information about that project. 17 | 18 | ## Requirements 19 | 20 | The only requirement is an appropriate JDK (JDK 11 as of writing) available on the system path. 21 | 22 | # How to Build 23 | 24 | This repo is structured as a composite [gradle](http://gradle.org) build. It includes the gradlew wrapper scripts for linux/mac/windows to enable dependency-free assembly. All gradle commands should be executed using the wrapper appropriate for the platform being built on. This generally means executing tasks via `gradlew.bat` for Windows, and the `gradlew` script on Linux/Mac. Note that a `build` on Windows, Mac or Linux will output artifacts useful for all supported platforms, regardless of which platform the build is executed on. However, the native image binaries of the CLI subproject are only created for the platform on which the build runs, and this must be executed on each platform for which a binary is desired. 25 | 26 | To build all projects, from the root of the repository: 27 | 28 | ``` 29 | // windows 30 | gradlew.bat build 31 | 32 | // mac/linux 33 | ./gradlew build 34 | 35 | ``` 36 | 37 | The output of each assembly will exist in the local subproject's `build` directory, which is created during build 38 | execution. A specific/individual project can be built by executing `./gradlew build` in the appropriate project 39 | subdirectory, or by using the task rules as described below. 40 | 41 | By default, the following tasks are executable from the root directory using the gradle wrapper script: 42 | 43 | * build 44 | * clean 45 | * assemble 46 | * tasks 47 | * test 48 | * check 49 | * intTest (maps to 'integrationTest' of the subprojects) 50 | 51 | 52 | ## Task Rules 53 | 54 | For convenience, task rules have been created at the root level, to be able to run specific subproject tasks without 55 | needing to change working directories in your terminal. The rules are as follows (for all examples, replace `./gradlew` 56 | with `gradlew.bat` for windows: 57 | 58 | ```shell 59 | // run task named 'taskName' on the plugin subproject 60 | ./gradlew pluginTaskName 61 | 62 | // run task named 'taskName' on the generator-core subproject 63 | ./gradlew genTaskName 64 | 65 | // run task named 'taskName' on the generator-cli subproject 66 | ./gradlew cliTaskName 67 | ``` 68 | 69 | For example, to run the `spotlessApply` task on the generator-cli subproject, execute `./gradlew cliSpotlessApply`. 70 | 71 | To see a list of all available tasks, run `./gradlew tasks` (`gradlew.bat tasks` on Windows). 72 | 73 | 74 | ## Questions? Feedback? Want to Contribute? 75 | 76 | Open an Issue! 77 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | base 3 | } 4 | 5 | val test by tasks.registering 6 | val intTest by tasks.registering 7 | 8 | /** 9 | * Bind top-level tasks to sub-build tasks for convenience and improved IDE behavior 10 | */ 11 | tasks { 12 | build { 13 | dependsOn(pluginTask("build")) 14 | dependsOn(generatorCoreTask("build")) 15 | dependsOn(generatorCliTask("build")) 16 | } 17 | assemble { 18 | dependsOn(pluginTask("assemble")) 19 | dependsOn(generatorCoreTask("assemble")) 20 | dependsOn(generatorCliTask("assemble")) 21 | } 22 | test { 23 | dependsOn(pluginTask("test")) 24 | dependsOn(generatorCoreTask("test")) 25 | dependsOn(generatorCliTask("test")) 26 | } 27 | intTest { 28 | dependsOn(pluginTask("integrationTest")) 29 | dependsOn(generatorCoreTask("integrationTest")) 30 | dependsOn(generatorCliTask("integrationTest")) 31 | } 32 | check { 33 | dependsOn(pluginTask("check")) 34 | dependsOn(generatorCoreTask("check")) 35 | dependsOn(generatorCliTask("check")) 36 | } 37 | clean { 38 | dependsOn(pluginTask("clean")) 39 | dependsOn(generatorCoreTask("clean")) 40 | dependsOn(generatorCliTask("clean")) 41 | } 42 | tasks { 43 | dependsOn(pluginTask("tasks")) 44 | dependsOn(generatorCoreTask("tasks")) 45 | dependsOn(generatorCliTask("tasks")) 46 | } 47 | addRule("Pattern: plugin") { 48 | val taskName: String = this 49 | if (taskName.startsWith("plugin")) { 50 | create(taskName) { 51 | dependsOn(pluginTask(taskName.replace("plugin", "").decapitalize())) 52 | } 53 | } 54 | } 55 | addRule("Pattern: gen") { 56 | val taskName:String = this 57 | if (taskName.startsWith("gen")) { 58 | create(taskName) { 59 | dependsOn(generatorCoreTask(taskName.replace("gen", "").decapitalize())) 60 | } 61 | } 62 | } 63 | addRule("Pattern: cli") { 64 | val taskName:String = this 65 | if (taskName.startsWith("cli")) { 66 | create(taskName) { 67 | dependsOn(generatorCliTask( taskName.replace("cli", "").decapitalize())) 68 | } 69 | } 70 | } 71 | } 72 | 73 | /* returns a reference to a task from the app(lication) build */ 74 | fun pluginTask(task: String): TaskReference { 75 | return gradle.includedBuild("gradle-module-plugin").task(":$task") 76 | } 77 | fun generatorCoreTask(task: String): TaskReference { 78 | return gradle.includedBuild("generator").task(":generator-core:$task") 79 | } 80 | fun generatorCliTask(task: String): TaskReference { 81 | return gradle.includedBuild("generator").task(":generator-cli:$task") 82 | } 83 | -------------------------------------------------------------------------------- /generator/README.md: -------------------------------------------------------------------------------- 1 | # Ignition Module Generator 2 | 3 | Consists of two projects useful in generating the boilerplate structure for an Ignition module built using the 4 | Gradle build tool. 5 | 6 | 1. [Ignition Module Generator Core](generator-core/README.md): library for creating Ignition module skeletons. Written in Kotlin, usable in Kotlin, Java and other JVM languages via jar-based dependency available as Maven artifacts. 7 | 8 | 2. [Module Generator CLI](generator-cli/README.md): command line application which uses the generator library to provide a _command line interface_ (CLI) that can be used to create modules from your operating system's terminal/console. 9 | 10 | ## Usage 11 | 12 | #### Building the projects 13 | 14 | To build the projects, cd into `/generator` and run the `build` task via the included gradle wrapper: 15 | 16 | ```shell 17 | // posix-style systems like Mac/Linux 18 | ./gradlew build 19 | ``` 20 | 21 | ``` 22 | // windows 23 | gradlew.bat build 24 | ``` 25 | 26 | ### Running the CLI 27 | 28 | To run the CLI locally, cd into the `generator/` directory and run `./gradlew runCli --console plain` task, (windows 29 | users, use the _bat_ wrappers as shown above. `--console plain ` is optional but suggested to avoid interpolation of 30 | gradle console logging while the commandline prompts for user input. 31 | -------------------------------------------------------------------------------- /generator/build.gradle.kts: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | // axion release plugin is not available in gradle plugin portal, add maven central to resolve 4 | mavenCentral() 5 | } 6 | 7 | // required due to spotless and axion-release having conflicting versions of jgit on the path 8 | // see https://github.com/diffplug/spotless/issues/587 9 | // and https://github.com/allegro/axion-release-plugin/issues/343 10 | configurations.classpath { 11 | resolutionStrategy { 12 | force("org.eclipse.jgit:org.eclipse.jgit:5.7.0.202003110725-r") 13 | } 14 | } 15 | } 16 | 17 | plugins { 18 | base 19 | kotlin("jvm") version "1.7.10" apply false 20 | id("org.jetbrains.dokka") version "1.5.31" apply false 21 | id("com.diffplug.spotless") version "6.11.0" apply false 22 | } 23 | 24 | allprojects { 25 | project.version = "0.4.0" 26 | } 27 | 28 | tasks { 29 | wrapper { 30 | distributionType = Wrapper.DistributionType.ALL 31 | } 32 | } 33 | 34 | val runNativeCli by tasks.registering(Exec::class) { 35 | dependsOn(":generator-cli:nativeImage") 36 | workingDir("$projectDir/module-generator/cli/build/graal") 37 | commandLine("./ignition-module-gen") 38 | } 39 | 40 | val runCli by tasks.registering { 41 | dependsOn(":generator-cli:run") 42 | } 43 | -------------------------------------------------------------------------------- /generator/generator-cli/README.md: -------------------------------------------------------------------------------- 1 | # Ignition Module Generator CLI 2 | 3 | A command-line interface for creating skeleton projects for Inductive Automation's Ignition platform. 4 | 5 | 6 | # How to Use 7 | 8 | Download the latest release for your platform from the releases tab, and simply run it. Follow the prompts to 9 | customize the module's initial setup. 10 | 11 | 12 | # Building 13 | 14 | This project uses Gradle for build tooling and includes the gradle wrapper. To build, simply execute `./gradlew 15 | build` on posix machines, or `gradle.bat build` on Windows. The wrapper will download all necessary dependencies 16 | from the primary gradle.org server and then execute the build using the correct version of the grade build tool. 17 | 18 | ### Requirements 19 | 20 | - *JDK 11* should resolve automatically via the [Gradle Java Toolchain](https://docs.gradle.org/current/userguide/toolchains.html) api. To explicitly specify JDK available to gradle, you have a few options: rely on the `JAVA_HOME` environmental variable, set `org.gradle.java.home=/path/to/jdk/home` as a commandline flag (`-Dorg.gradle.java.home=/path`), or specify the `org.gradle.java.home` in a [gradle.properties](https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties) file. 21 | 22 | 23 | ### How To 24 | 25 | 26 | To see a list of all tasks available, run `./gradlew tasks` or if on Windows, `gradle.bat tasks`. 27 | 28 | 29 | # Native Images 30 | 31 | The CLI project uses [Palantir's Gradle Graal plugin](https://github.com/palantir/gradle-graal) to generate a native 32 | binary executable for the platform running the build. Mac and Linux users can simply run `./gradlew :nativeImage 33 | ` in the cli project's directory, and a native binary will be created in the `module-generator/cli/build/graal 34 | ` directory. While module authors will require a JDK to build modules, this binary will execute and generate 35 | module projects without any installed JVM/JRE. 36 | 37 | Windows requires some setup for building graal native images. [Chocolatey](https://chocolatey.org/install) package 38 | manager enables some easy configuration. Follow these steps to configure your Windows environment for building graal 39 | native images (tested on Windows 10 Pro): 40 | 41 | ``` 42 | choco install visualstudio2019-workload-vctools windows-sdk-7.1 kb2519277 43 | ``` 44 | 45 | If an appropriate JDK is not installed, you may install a compatible JDK using chocolatey as well (note this may change 46 | your system PATH if you have a different JDK already installed): 47 | 48 | ``` 49 | choco install adoptopenjdk11 50 | ``` 51 | 52 | If you encounter errors relating 'missing Windows 7.1 SDK', you can try specifying the visual studio version (2019 if following the install instructions just above), or provide the path to the appropriate `vsvars64.bat` by using the graal plugin configuration as [documented at the plugin repo](https://github.com/palantir/gradle-graal). 53 | 54 | See the official [Graal Native-Image docs](https://www.graalvm.org/docs/reference-manual/native-image/) for details on 55 | environmental prerequisites. 56 | 57 | 58 | ## Questions? Feedback? Want to Contribute? 59 | 60 | Open an Issue. 61 | -------------------------------------------------------------------------------- /generator/generator-cli/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | 3 | plugins { 4 | application 5 | // Apply the Kotlin JVM plugin to add support for Kotlin. 6 | kotlin("jvm") 7 | // kapt annotation processor plugin, for picocli/graal annotations 8 | kotlin("kapt") 9 | // Apply the application plugin to add support for building a CLI application. 10 | id("com.palantir.graal") version "0.9.0" 11 | id("com.github.johnrengelman.shadow") version "7.0.0" 12 | id("com.diffplug.spotless") 13 | } 14 | 15 | repositories { 16 | mavenLocal() 17 | mavenCentral() 18 | } 19 | 20 | java { 21 | withJavadocJar() 22 | withSourcesJar() 23 | 24 | toolchain { 25 | this.languageVersion.set(JavaLanguageVersion.of(11)) 26 | } 27 | } 28 | 29 | kotlin { 30 | jvmToolchain { 31 | (this as JavaToolchainSpec).languageVersion.set(JavaLanguageVersion.of(11)) 32 | } 33 | } 34 | 35 | 36 | group = "io.ia.sdk.tools.module.gen" 37 | 38 | dependencies { 39 | // Align versions of all Kotlin components 40 | implementation(platform("org.jetbrains.kotlin:kotlin-bom")) 41 | implementation(projects.generatorCore) 42 | // Use the Kotlin JDK 8 standard library. 43 | implementation(libs.picoCli) 44 | implementation(libs.slf4jSimple) 45 | // kapt used for annotation processing when creating the native image 46 | kapt(libs.picoCliCodegen) 47 | compileOnly(libs.slf4jApi) 48 | // Use the Kotlin test library. 49 | testImplementation(kotlin("test-junit")) 50 | } 51 | 52 | val JVM_TARGET = "1.8" 53 | val APP_MAIN_CLASS = "io.ia.sdk.tools.module.cli.ModuleGeneratorCli" 54 | 55 | application { 56 | // Define the main class for the application 57 | this.mainClass.set(APP_MAIN_CLASS) 58 | } 59 | 60 | spotless { 61 | kotlin { 62 | // Optional user arguments can be set as such: 63 | ktlint().editorConfigOverride(mapOf("indent_size" to "4", "continuation_indent_size" to "4")) 64 | } 65 | } 66 | 67 | 68 | val reflectionConfigFile = 69 | "${buildDir}/resources/main/META-INF/native-image/${project.group}/${project.name}/reflect-config.json" 70 | val resourceConfigFile = "src/main/resources/resource-config.json" 71 | 72 | val binaryName = "ignition-module-gen" 73 | 74 | graal { 75 | javaVersion("11") 76 | graalVersion("20.3.5") 77 | mainClass(APP_MAIN_CLASS) 78 | outputName(binaryName) 79 | windowsVsVersion("2019") 80 | 81 | /* 82 | * Each option must be its own line-item, all will get added to the final options command args passed to the 83 | * substrate VM compiler 84 | */ 85 | 86 | // tell graal/substrate to load resources that need to resolve via `Classloader.getSystemResource` style resolution 87 | // we define the patterns we want to support in the config json file found below, in accordance with 88 | // https://github.com/oracle/graal/blob/master/substratevm/OPTIONS.md 89 | option("-H:ResourceConfigurationFiles=$resourceConfigFile") 90 | 91 | // don"t fallback to "jre-required" image if the full native image assembly fails 92 | option("--no-fallback") 93 | 94 | // we don"t need this because we generate these dynamically at build-time, left for future reference 95 | // option("-H:ReflectionConfigurationFiles=$reflectionConfigFile") 96 | } 97 | 98 | tasks { 99 | compileKotlin { 100 | kotlinOptions { 101 | jvmTarget = JVM_TARGET 102 | // will retain parameter names for java reflection 103 | javaParameters = true 104 | } 105 | } 106 | 107 | compileTestKotlin { 108 | kotlinOptions { 109 | jvmTarget = JVM_TARGET 110 | javaParameters = true 111 | } 112 | } 113 | nativeImage { 114 | dependsOn(build) 115 | } 116 | named("run") { 117 | standardInput = System.`in` 118 | } 119 | named("runShadow") { 120 | standardInput = System.`in` 121 | } 122 | } 123 | 124 | val runNative by tasks.registering(Exec::class) { 125 | workingDir("$buildDir/graal") 126 | commandLine(binaryName) 127 | dependsOn(tasks.nativeImage) 128 | } 129 | -------------------------------------------------------------------------------- /generator/generator-cli/src/main/resources/resource-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "resources": [ 3 | {"pattern": "templates/.*"}, 4 | {"pattern": "gradle/wrapper/.*"} 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /generator/generator-cli/src/test/kotlin/io/ia/sdk/tools/module/cli/ModuleGeneratorCliTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This Kotlin source file was generated by the Gradle 'init' task. 3 | */ 4 | package io.ia.sdk.tools.module.cli 5 | 6 | import kotlin.test.Test 7 | import kotlin.test.assertNotNull 8 | 9 | class ModuleGeneratorCliTest { 10 | @Test 11 | fun `test app initializes class without error`() { 12 | val classUnderTest = ModuleGeneratorCli() 13 | assertNotNull(classUnderTest, "app should have a scope") 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /generator/generator-core/README.md: -------------------------------------------------------------------------------- 1 | # Ignition Module Generator 2 | 3 | A library for generating boilerplate folder/file structures for gradle-based Ignition Module projects. 4 | 5 | ## Why 6 | 7 | We've had requests to make it easier to get started with module development using Gradle. So when we started writing 8 | functional tests for 9 | the [Gradle Module Plugin](../../gradle-module-plugin) 10 | and realized that we were generating module projects, we decided to pull out the functions and make them an independent 11 | library. The result is a somewhat unpolished but functional codebase, owing to its roots as simple testing support. 12 | 13 | While the modules it generates may not be 100% ready for production (nor do they absolve one from learning Gradle!), it 14 | can save a lot of time getting started. 15 | 16 | 17 | ## Adding to Your Project as a Dependency 18 | 19 | This small library is intended for use in a Java runtime environment, and is not currently published to a public 20 | artifact repository. To use in a maven or gradle project dependency, you can build and publish it to your local maven 21 | cache. To publish to your local maven cache (by default, in `/.m2/`), run: 22 | 23 | 24 | ```shell 25 | // for posix systems 26 | ./gradlew build publishToMavenLocal 27 | ``` 28 | 29 | ``` 30 | // for windows cmd 31 | gradlew.bat build publishToMavenLocal 32 | ``` 33 | 34 | Once published to maven's local cache, you may consume the artifact by adding a dependency on it to your project. To 35 | resolve the artifact, you'll need `mavenLocal()` as a valid gradle repository. 36 | 37 | 38 | #### Adding to a Gradle buildscript 39 | 40 | ```kotlin 41 | // gradle example for kotlin buildscripts , in build.gradle.kts of project depending on the generator-core 42 | dependencies { 43 | repositories { 44 | mavenLocal() 45 | mavenCentral() 46 | } 47 | } 48 | 49 | dependencies { 50 | // VERSION is the version defined in generator/build.gradle.kts 51 | implementation("io.ia.sdk.tools.module.gen:generator-core:$VERSION") 52 | } 53 | ``` 54 | 55 | #### Adding to Maven pom.xml 56 | 57 | ``` 58 | ... 59 | 60 | io.ia.sdk.tools.module.gen 61 | generator-core 62 | $VERSION 63 | 64 | 65 | ``` 66 | 67 | ## Using 68 | 69 | > Note: If you just want to generate a module structure without using this library in your own project, check out the [Module Generator CLI](https://github.com/inductiveautomation/ignition-module-tools/tree/master/generator/generator-cli#ignition-module-generator-cli) project. It provides some basic functionality to get a project established. 70 | 71 | To generate a module, simple create a GeneratorConfig by using the provided builder, and then call 72 | the `generate(config)` function. 73 | 74 | 75 | ```java 76 | 77 | import io.ia.ignition.module.generator.ModuleGenerator; 78 | import io.ia.ignition.module.generator.api.GeneratorConfig.ConfigBuilder; 79 | 80 | public class Demo { 81 | public static void main(String[] args) { 82 | Path parentDir = Paths.get(System.getProperty("user.home") + "/ignition/modules"); 83 | 84 | ConfigBuilder builder = new ConfigBuilder(); 85 | builder.moduleName("Great Stuff"); 86 | builder.scopes("G"); 87 | builder.packageName("my.pkg.name"); 88 | builder.parentDir(parentDir); 89 | 90 | Path moduleRoot = ModuleGenerator.generate(builder.build()); 91 | 92 | System.out.println("New module generated at " + moduleRoot.toString()); 93 | } 94 | } 95 | ``` 96 | 97 | ## Building 98 | 99 | This project uses Gradle for build tooling and includes the gradle wrapper. To build, simply execute `./gradlew build` 100 | on posix machines, or `gradle.bat build` on Windows. The wrapper will download all necessary dependencies from the 101 | primary gradle.org server and then execute the build using the correct version of the grade build tool. The resulting 102 | jar will be available in the `generator-core/build/libs` directory 103 | 104 | To see a list of all tasks available, run `./gradlew tasks` or if on Windows, `gradle.bat tasks`. 105 | 106 | ## Publishing to Artifact Repo 107 | 108 | The assembled library may be published to an artifact repository by configuring the appropriate publishing settings. 109 | By default, it is configured to publish to a maven repository. To publish using this default setup, set the following 110 | properties (with appropriate values) as environmental parameters. This is most easily done with a 'gradle.properties' 111 | file, which can reside in the root of this repository, or in your user '.gradle' directory ( 112 | `~/.gradle/gradle.properties` in posix systems, typically `C:\Users\username\.gradle\gradle.properties` on Windows). 113 | 114 | ``` 115 | ignitionModuleGen.maven.repo.snapshot.name= 116 | ignitionModuleGen.maven.repo.snapshot.url= 117 | ignitionModuleGen.maven.repo.snapshot.username=< 118 | ignitionModuleGen.maven.repo.snapshot.password= 119 | ignitionModuleGen.maven.repo.release.name= 120 | ignitionModuleGen.maven.repo.release.url= 121 | ignitionModuleGen.maven.repo.release.username= 122 | ignitionModuleGen.maven.repo.release.password= 123 | ``` 124 | 125 | ## Roadmap 126 | 127 | ### Planned 128 | 129 | * [x] Functional kotlin buildscript generation 130 | * [ ] Fully functional kotlin-based modules 131 | * [ ] kotlin module sources 132 | 133 | 134 | ## Contributing 135 | 136 | Contributions are welcome: Open an Issue to discuss your ideas, or submit a PR for feedback! 137 | -------------------------------------------------------------------------------- /generator/generator-core/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.gradle.internal.impldep.org.junit.experimental.categories.Categories.CategoryFilter.include 2 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 3 | 4 | plugins { 5 | `java-library` 6 | `maven-publish` 7 | signing 8 | kotlin("jvm") 9 | id("org.jetbrains.dokka") 10 | id("com.diffplug.spotless") 11 | } 12 | 13 | group = "io.ia.sdk.tools.module.gen" 14 | 15 | repositories { 16 | mavenLocal() 17 | mavenCentral() 18 | } 19 | 20 | java { 21 | withJavadocJar() 22 | withSourcesJar() 23 | 24 | toolchain { 25 | this.languageVersion.set(JavaLanguageVersion.of(11)) 26 | } 27 | } 28 | 29 | kotlin { 30 | jvmToolchain { 31 | (this as JavaToolchainSpec).languageVersion.set(JavaLanguageVersion.of(11)) 32 | } 33 | } 34 | 35 | 36 | tasks { 37 | withType(KotlinCompile::class) { 38 | kotlinOptions { 39 | javaParameters = true 40 | } 41 | } 42 | 43 | withType(JavaCompile::class) { 44 | options.encoding = "UTF-8" 45 | } 46 | 47 | named("compileTestKotlin") { 48 | // don't try compiling resources that somehow end up in the test compilation path when we add the integration 49 | // test suite 50 | exclude("**/resources/**/*.kts") 51 | } 52 | } 53 | 54 | dependencies { 55 | // Align versions of all Kotlin components 56 | implementation(platform("org.jetbrains.kotlin:kotlin-bom")) 57 | // Use SLF4J api for logging, logger implementation to be provided by lib consumer 58 | api(libs.slf4jApi) 59 | 60 | // Use the Kotlin test library. 61 | // testImplementation(libs.bundles.kotlinTest) 62 | testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") 63 | testImplementation(kotlin("test-junit")) 64 | 65 | // support logging in tests 66 | testImplementation(libs.slf4jApi) 67 | testImplementation(libs.slf4jSimple) 68 | } 69 | 70 | testing { 71 | suites { 72 | val test by getting(JvmTestSuite::class) 73 | 74 | val integrationTest by registering(JvmTestSuite::class) { 75 | // useKotlinTest() 76 | dependencies { 77 | implementation(project()) 78 | implementation(libs.kotlinTestJunit) 79 | } 80 | 81 | targets { 82 | all { 83 | testTask.configure { 84 | shouldRunAfter(test) 85 | testClassesDirs = sourceSets.named("integrationTest").get().output.classesDirs 86 | } 87 | } 88 | } 89 | } 90 | } 91 | } 92 | 93 | tasks.named("check") { 94 | dependsOn(testing.suites.named("integrationTest")) 95 | } 96 | 97 | spotless { 98 | kotlin { 99 | // optionally takes a version 100 | ktlint("0.44.0").editorConfigOverride(mapOf("ktlint_disabled_rules" to "filename")) 101 | 102 | targetExclude( 103 | "src/main/resources/templates/config/*.kts", 104 | "src/main/resources/templates/buildscript/*.build.gradle.kts", 105 | "src/main/resources/templates/settings.gradle.kts", 106 | "src/main/resources/templates/hook/*.kt" 107 | ) 108 | } 109 | } 110 | 111 | // Artifact publishing configuration 112 | publishing { 113 | publications { 114 | create("ignitionModuleGenerator") { 115 | from(components["java"]) 116 | this.version = project.version.toString() 117 | this.groupId = project.group.toString() 118 | this.artifactId = project.name 119 | 120 | } 121 | } 122 | 123 | val PUBLISHING_KEY = "ignitionModuleGen.maven.repo.${if ("$version".contains("-SNAPSHOT")) "snapshot" else "release"}" 124 | 125 | repositories { 126 | maven { 127 | name = "${PUBLISHING_KEY}.name".load() 128 | url = uri("${PUBLISHING_KEY}.url".load()) 129 | 130 | credentials { 131 | username = "${PUBLISHING_KEY}.username".load() 132 | password = "${PUBLISHING_KEY}.password".load() 133 | } 134 | } 135 | } 136 | } 137 | 138 | 139 | /** 140 | * Returns the gradle property value associated with the string, if present. Otherwise, returns the string "null". 141 | * 142 | * The properties may be defined any way described in the corresponding Gradle Properties docs at 143 | * https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties 144 | * 145 | * Suggestion using either: 146 | * 147 | * 1. Providing them at task runtime via `-Psome.property.key=value` 148 | * 2. gradle.properties file located in your gradle home dir (default: `~/.gradle/gradle.properties`) 149 | */ 150 | fun String.load(): String { 151 | return project.rootProject.properties[this]?.toString() ?: "null" 152 | } 153 | -------------------------------------------------------------------------------- /generator/generator-core/src/integrationTest/kotlin/io/ia/ignition/module/generator/IntegrationTests.kt: -------------------------------------------------------------------------------- 1 | package io.ia.ignition.module.generator 2 | 3 | import io.ia.ignition.module.generator.api.GeneratorConfigBuilder 4 | import io.ia.ignition.module.generator.api.GradleDsl 5 | import org.junit.Rule 6 | import org.junit.Test 7 | import org.junit.rules.TemporaryFolder 8 | import java.nio.file.Files 9 | import java.nio.file.Path 10 | import java.nio.file.attribute.PosixFileAttributes 11 | import java.nio.file.attribute.PosixFilePermission 12 | import java.util.concurrent.TimeUnit 13 | import kotlin.test.assertTrue 14 | 15 | class IntegrationTests { 16 | @get:Rule 17 | val tempFolder = TemporaryFolder() 18 | 19 | data class TestConfig(val moduleName: String, val packageName: String, val scope: String, val dir: Path) 20 | 21 | private fun dir(folderName: String): Path { 22 | return tempFolder.newFolder(folderName).toPath() 23 | } 24 | 25 | private enum class OS { 26 | NIXLIKE, WIN; 27 | } 28 | 29 | private fun os(): OS { 30 | val os = System.getProperty("os.name").lowercase() 31 | return when { 32 | os.contains("win") -> OS.WIN 33 | os.contains("nix") || os.contains("nux") || os.contains("aix") || os.contains("mac") -> { 34 | OS.NIXLIKE 35 | } 36 | 37 | else -> throw Exception("Could not resolve platform from system property 'os.name' with value: $os") 38 | } 39 | } 40 | 41 | fun command(taskConfig: String): Set { 42 | return when (os()) { 43 | OS.WIN -> setOf("cmd.exe", "/c", "gradlew.bat $taskConfig") 44 | OS.NIXLIKE -> setOf("sh", "-c", "./gradlew $taskConfig") 45 | } 46 | } 47 | 48 | private fun applyExecPermissions(file: Path) { 49 | val perms: MutableSet = Files.readAttributes( 50 | file, 51 | PosixFileAttributes::class.java 52 | ).permissions() 53 | 54 | perms.add(PosixFilePermission.OWNER_WRITE) 55 | perms.add(PosixFilePermission.OWNER_READ) 56 | perms.add(PosixFilePermission.OWNER_EXECUTE) 57 | perms.add(PosixFilePermission.GROUP_WRITE) 58 | perms.add(PosixFilePermission.GROUP_READ) 59 | perms.add(PosixFilePermission.GROUP_EXECUTE) 60 | perms.add(PosixFilePermission.OTHERS_WRITE) 61 | perms.add(PosixFilePermission.OTHERS_READ) 62 | perms.add(PosixFilePermission.OTHERS_EXECUTE) 63 | 64 | Files.setPosixFilePermissions(file, perms) 65 | } 66 | 67 | private fun String.runCommand(workingDir: Path): String { 68 | if (os() == OS.NIXLIKE) { 69 | // set group/all user permissions so script can exec 70 | applyExecPermissions(workingDir.resolve("gradlew")) 71 | } 72 | val cmd = command(this) 73 | var result: String = "" 74 | 75 | ProcessBuilder(*(cmd.toTypedArray())) 76 | .directory(workingDir.toFile()) 77 | .redirectOutput(ProcessBuilder.Redirect.PIPE) 78 | .redirectErrorStream(true) 79 | .start().also { process -> 80 | process.inputStream.bufferedReader().run { 81 | while (true) { 82 | readLine()?.let { line -> 83 | result += "$line\n" 84 | } ?: break 85 | } 86 | } 87 | 88 | process.waitFor(60, TimeUnit.SECONDS) 89 | if (process.isAlive) { 90 | result += "TIMEOUT occurred\n" 91 | process.destroy() 92 | } 93 | } 94 | 95 | return result 96 | } 97 | 98 | @Test 99 | fun `generated groovy buildscript projects build successfully`() { 100 | listOf( 101 | TestConfig("The Greatness", "le.examp", "G", dir("v1")), 102 | TestConfig("almost Greatness", "le.examp.odd", "GC", dir("v2")), 103 | TestConfig("The greatness", "le.examp.whoa", "GCD", dir("v3")), 104 | TestConfig("oncegreatness", "buenos.dias.amigo", "GCD", dir("v4")), 105 | TestConfig("The Greatness", "le.pant", "CD", dir("v5")), 106 | TestConfig("A Goodness", "come.va", "C", dir("v6")), 107 | TestConfig("The number 1 Greatness", "bon.gior.nio", "D", dir("v7")) 108 | ).forEach { 109 | val config = GeneratorConfigBuilder() 110 | .moduleName(it.moduleName) 111 | .packageName(it.packageName) 112 | .parentDir(it.dir) 113 | .scopes(it.scope) 114 | .buildscriptDsl(GradleDsl.GROOVY) 115 | .build() 116 | 117 | val projectRootDir: Path = ModuleGenerator.generate(config) 118 | 119 | val processOutput = "build".runCommand(projectRootDir) 120 | assertTrue(processOutput.contains("BUILD SUCCESSFUL")) 121 | } 122 | } 123 | 124 | @Test 125 | fun `generated kotlin buildscript projects build successfully`() { 126 | listOf( 127 | TestConfig("The Greatness", "le.examp", "G", dir("v1_kts")), 128 | TestConfig("almost Greatness", "le.examp.odd", "GC", dir("v2_kts")), 129 | TestConfig("The greatness", "le.examp.whoa", "GCD", dir("v3_kts")), 130 | TestConfig("oncegreatness", "buenos.dias.amigo", "GCD", dir("v4_kts")), 131 | TestConfig("The Greatness", "le.pant", "CD", dir("v5_kts")), 132 | TestConfig("A Goodness", "come.va", "C", dir("v6_kts")), 133 | TestConfig("The number 1 Greatness", "bon.gior.nio", "D", dir("v7_kts")) 134 | ).forEach { 135 | val config = GeneratorConfigBuilder() 136 | .moduleName(it.moduleName) 137 | .packageName(it.packageName) 138 | .parentDir(it.dir) 139 | .scopes(it.scope) 140 | .buildscriptDsl(GradleDsl.KOTLIN) 141 | .build() 142 | 143 | val projectRootDir: Path = ModuleGenerator.generate(config) 144 | 145 | val processOutput = "build".runCommand(projectRootDir) 146 | println("OUTPUT:\n$processOutput") 147 | assertTrue(processOutput.contains("BUILD SUCCESSFUL")) 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/kotlin/io/ia/ignition/module/generator/api/DefaultDependencies.kt: -------------------------------------------------------------------------------- 1 | package io.ia.ignition.module.generator.api 2 | 3 | import io.ia.ignition.module.generator.api.TemplateMarker.SDK_VERSION_PLACEHOLDER 4 | 5 | object DefaultDependencies { 6 | 7 | // default gradle version 8 | const val GRADLE_VERSION = "7.6" 9 | 10 | // default plugin configuration for the root build.gradle 11 | val MODL_PLUGIN: String = "id(\"io.ia.sdk.modl\") version(\"${TemplateMarker.MODL_PLUGIN_VERSION.key}\")" 12 | 13 | // example 14 | // "com.inductiveautomation.ignitionsdk:client-api:${'$'}{sdk_version}" 15 | val ARTIFACTS: Map> = mapOf( 16 | ProjectScope.CLIENT to setOf( 17 | "com.inductiveautomation.ignitionsdk:client-api:$SDK_VERSION_PLACEHOLDER", 18 | "com.inductiveautomation.ignitionsdk:vision-client-api:$SDK_VERSION_PLACEHOLDER", 19 | "com.inductiveautomation.ignitionsdk:ignition-common:$SDK_VERSION_PLACEHOLDER" 20 | ), 21 | ProjectScope.COMMON to setOf( 22 | "com.inductiveautomation.ignitionsdk:ignition-common:$SDK_VERSION_PLACEHOLDER" 23 | ), 24 | ProjectScope.DESIGNER to setOf( 25 | "com.inductiveautomation.ignitionsdk:designer-api:$SDK_VERSION_PLACEHOLDER", 26 | "com.inductiveautomation.ignitionsdk:ignition-common:$SDK_VERSION_PLACEHOLDER" 27 | ), 28 | ProjectScope.GATEWAY to setOf( 29 | "com.inductiveautomation.ignitionsdk:ignition-common:$SDK_VERSION_PLACEHOLDER", 30 | "com.inductiveautomation.ignitionsdk:gateway-api:$SDK_VERSION_PLACEHOLDER" 31 | ) 32 | ) 33 | 34 | fun Set.toDependencyFormat( 35 | dsl: GradleDsl, 36 | configuration: String = "compileOnly" 37 | ): String { 38 | return map { artifact -> 39 | val version = dsl.artifactSdkVersion() 40 | "$configuration(\"${artifact.replace(SDK_VERSION_PLACEHOLDER.toString(), version)}\")" 41 | }.joinToString(separator = "\n ") 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/kotlin/io/ia/ignition/module/generator/api/GeneratorConfig.kt: -------------------------------------------------------------------------------- 1 | package io.ia.ignition.module.generator.api 2 | 3 | import io.ia.ignition.module.generator.api.DefaultDependencies.GRADLE_VERSION 4 | import io.ia.ignition.module.generator.api.GradleDsl.KOTLIN 5 | import io.ia.ignition.module.generator.api.SourceFileType.JAVA 6 | import java.nio.file.Path 7 | 8 | /** 9 | * Configuration object that the generator uses to construct the module structure. Create via the 10 | * [GeneratorConfigBuilder]. 11 | */ 12 | data class GeneratorConfig constructor( 13 | /** 14 | * Name of the module 15 | */ 16 | val moduleName: String, 17 | 18 | /** 19 | * The package name which all subprojects will be based on. 20 | */ 21 | val packageName: String, 22 | 23 | /** 24 | * The shorthand initials for the scopes that this module should be created with 25 | */ 26 | val scopes: String, 27 | 28 | /** 29 | * Where the new root directory for the module should be created. 30 | */ 31 | val parentDir: Path, 32 | 33 | /** 34 | * Language the Gradle settings file should be created with. 35 | */ 36 | val settingsDsl: GradleDsl = KOTLIN, 37 | 38 | /** 39 | * Which language/dsl the Gradle build files should be created with. 40 | */ 41 | val buildDsl: GradleDsl = KOTLIN, 42 | 43 | /** 44 | * Which language the module should be written in. 45 | */ 46 | val projectLanguage: SourceFileType = JAVA, 47 | 48 | /** 49 | * Version of gradle wrapper to create the project with. Must be a version that has appropriate gradle scripts 50 | * and wrapper jars available in the resources. 51 | */ 52 | val gradleWrapperVersion: String = GRADLE_VERSION, 53 | 54 | /** 55 | * Whether to inject debuggable (`mavenLocal()`) pluginManagament repository sources. 56 | */ 57 | val debugPluginConfig: Boolean = false, 58 | 59 | /** 60 | * Value applied to the root plugin configuration. 61 | * 62 | * By default, a multi-scope (or single-scope with multi-project structure) root buildscript file will have only 63 | * the module plugin applied. In a single directory/single scope project structure, it will also have the 64 | * appropriate java/kotlin plugins (`java-library` or `kotlin("jvm")`) applied, according to the project source 65 | * language. 66 | * 67 | * Setting this value to a non-empty string will override the default plugin configuration, replacing it entirely. 68 | */ 69 | val rootPluginConfig: String = "", 70 | 71 | /** 72 | * Map of strings that will be replaced if found in any of the resources file templates used to create a module 73 | * project. These will override any defaults or template replacement values, if present. Care should be taken 74 | * so that these replacements don't result in errors by unintentionally overriding the TemplateReplacement values 75 | * used internal by the generator. 76 | * 77 | * Generally only used in testing and development. 78 | */ 79 | val customReplacements: Map = emptyMap(), 80 | 81 | /** 82 | * If a single Ignition scope is declared, the generator defaults to the more flexible structure of having an 83 | * independent subproject, even in the case of a single scope. Set this value to `true` in order to create a 84 | * project which uses the root directory as the location for source code files as well. 85 | * 86 | * Example directory structures: 87 | * ``` 88 | * // when value is set to true for a G scoped module 89 | * 90 | * my-project/ 91 | * src/ 92 | * main/ 93 | * java/.../MyProjectGatewayHook.java 94 | * resources/someResource.jpg 95 | * settings.gradle 96 | * build.gradle 97 | * 98 | * 99 | * // default structure, when value set to false 100 | * 101 | * my-project/ 102 | * settings.gradle 103 | * build.gradle 104 | * gateway/ 105 | * src/ 106 | * main/ 107 | * java/.../MyProjectGatewayHook.java 108 | * resources/something.jpg 109 | * build.gradle 110 | * ``` 111 | */ 112 | val useRootProjectWhenSingleScope: Boolean = false, 113 | 114 | /** 115 | * Version of the module plugin to be used for the project. Is excluded when 'dev mode' module structure is 116 | * generated, as it is assumed the plugin will be established via 'includeBuild' in settings.gradle 117 | * pluginManagement. 118 | */ 119 | val modulePluginVersion: String = "0.4.0", 120 | 121 | /** 122 | * If signing the module should be required, set to false. Set to true by default to allow building the 123 | * generated module without needing to establish signing certificate configuration. Should be set to 'false' 124 | * in the generated project when building modules intended for production use. 125 | */ 126 | val skipModuleSigning: Boolean = true 127 | 128 | ) 129 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/kotlin/io/ia/ignition/module/generator/api/GeneratorConfigBuilder.kt: -------------------------------------------------------------------------------- 1 | package io.ia.ignition.module.generator.api 2 | 3 | import io.ia.ignition.module.generator.api.DefaultDependencies.GRADLE_VERSION 4 | import io.ia.ignition.module.generator.api.GradleDsl.GROOVY 5 | import io.ia.ignition.module.generator.api.SourceFileType.JAVA 6 | import org.slf4j.Logger 7 | import org.slf4j.LoggerFactory 8 | import java.io.File 9 | import java.nio.file.Path 10 | 11 | class GeneratorConfigBuilder { 12 | companion object { 13 | @JvmStatic 14 | val log: Logger = LoggerFactory.getLogger("GeneratorConfigBuilder") 15 | } 16 | 17 | private lateinit var moduleName: String 18 | private lateinit var packageName: String 19 | private lateinit var parentDir: Path 20 | private lateinit var scopes: String 21 | private var customReplacements: Map = emptyMap() 22 | private var buildDsl: GradleDsl = GROOVY 23 | private var projectLanguage: SourceFileType = JAVA 24 | private var settingsDsl: GradleDsl = GROOVY 25 | private var gradleWrapperVersion: String = GRADLE_VERSION 26 | private var debugPluginConfig: Boolean = false 27 | private var rootPluginConfig: String = "" 28 | private var useRootForSingleProjectScope: Boolean = false 29 | private var modulePluginVersion: String = "0.4.0" 30 | private var allowUnsignedModules: Boolean = false 31 | 32 | // builder methods 33 | fun buildscriptDsl(buildDsl: GradleDsl) = apply { this.buildDsl = buildDsl } 34 | fun debugPluginConfig(enable: Boolean) = apply { this.debugPluginConfig = enable } 35 | fun gradleWrapperVersion(version: String) = apply { this.gradleWrapperVersion = version } 36 | fun moduleName(name: String?) = apply { this.moduleName = name ?: "Example" } 37 | fun packageName(packageName: String?) = apply { this.packageName = packageName ?: "le.examp" } 38 | fun parentDir(dir: Path?) = apply { this.parentDir = dir ?: File("").toPath() } 39 | fun projectLanguage(language: String) = apply { 40 | this.projectLanguage = SourceFileType.valueOf(language.lowercase()) 41 | } 42 | 43 | fun rootPluginConfig(config: String) = apply { this.rootPluginConfig = config } 44 | fun scopes(scopes: String?) = apply { this.scopes = scopes ?: "" } 45 | fun settingsDsl(settingsDsl: GradleDsl) = apply { this.settingsDsl = settingsDsl } 46 | 47 | fun customReplacements(customReplacements: Map) = apply { 48 | this.customReplacements = customReplacements 49 | } 50 | 51 | fun useRootForSingleScopeProject(value: Boolean) = apply { 52 | this.useRootForSingleProjectScope = value 53 | } 54 | 55 | fun allowUnsignedModules(allow: Boolean = true) = apply { 56 | this.allowUnsignedModules = allow 57 | } 58 | 59 | // creates the config object 60 | fun build(): GeneratorConfig { 61 | log.debug("Construction GeneratorConfig...") 62 | return GeneratorConfig( 63 | moduleName = moduleName, 64 | packageName = packageName, 65 | scopes = scopes, 66 | parentDir = parentDir, 67 | settingsDsl = settingsDsl, 68 | buildDsl = buildDsl, 69 | projectLanguage = projectLanguage, 70 | gradleWrapperVersion = gradleWrapperVersion, 71 | debugPluginConfig = debugPluginConfig, 72 | rootPluginConfig = rootPluginConfig, 73 | customReplacements = this.customReplacements, 74 | useRootProjectWhenSingleScope = useRootForSingleProjectScope, 75 | modulePluginVersion = modulePluginVersion, 76 | skipModuleSigning = allowUnsignedModules 77 | ) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/kotlin/io/ia/ignition/module/generator/api/GeneratorContext.kt: -------------------------------------------------------------------------------- 1 | package io.ia.ignition.module.generator.api 2 | 3 | import java.nio.file.Path 4 | 5 | interface GeneratorContext { 6 | val config: GeneratorConfig 7 | 8 | /** 9 | * Map of [TemplateMarker.key] to appropriate replacement value 10 | */ 11 | fun getTemplateReplacements(): Map 12 | 13 | /** 14 | * The root 'workspace' directory, aka where the settings.gradle and root build.gradle will be created 15 | */ 16 | fun getRootDirectory(): Path 17 | 18 | /** 19 | * The name of the class hook, generated from the scope and the class prefix such that a 'gateway' scope, and a 20 | * validated module name of 'Splitting Historizer' would resolve to _SplittingHistorizerGatewayHook_ 21 | */ 22 | fun getHookClassName(scope: ProjectScope): String 23 | 24 | /** 25 | * Returns the valid class name created from the validated module name. 26 | * 27 | * ### Examples: 28 | * 29 | * * _Some Functionality_ becomes _SomeFunctionality_ 30 | * * _some functionality_ becomes _SomeFunctionality_ 31 | * * _Do The 123 Things_ becomes _DoThe123Things_ 32 | * * _I do cool stuff_ becomes _IDoCoolStuff_ 33 | * * _More Test Strings_ becomes _MoreTestStrings_ 34 | * 35 | */ 36 | fun getClassPrefix(): String 37 | 38 | /** 39 | * The name of the build file, generally determined by the build script type set in the [GeneratorConfig] 40 | */ 41 | fun getBuildScriptFilename(): String 42 | 43 | /** 44 | * The name of the build file, generally determined by the settings script type set in the [GeneratorConfig] 45 | */ 46 | fun getSettingFileName(): String 47 | 48 | /** 49 | * A set of Gradle Feature Preview strings, each of which will be added to the `settings.gradle` or 50 | * `settings.gradle.kts`. See the 51 | * [Gradle Feature Lifecycle](https://docs.gradle.org/current/userguide/feature_lifecycle.html) for additional 52 | * details. 53 | */ 54 | fun getFeaturePreviews(): Set { 55 | return emptySet() 56 | } 57 | 58 | /** 59 | * Returns the resource path for the boilerplate stub implementation of an Ignition hook class, or in the case of 60 | * a 'common' project, a simple empty `Module.java`. 61 | * 62 | * @param scope for the hook/stub class being generated 63 | */ 64 | fun getHookResourcePath(scope: ProjectScope): String 65 | 66 | /** 67 | * Returns the module id, which by default is "[GeneratorConfig.packageName] + '.' + [getClassPrefix]" 68 | */ 69 | fun getModuleId(): String 70 | } 71 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/kotlin/io/ia/ignition/module/generator/api/GradleDsl.kt: -------------------------------------------------------------------------------- 1 | package io.ia.ignition.module.generator.api 2 | 3 | enum class GradleDsl { 4 | GROOVY, 5 | KOTLIN; 6 | 7 | fun buildScriptFilename(): String { 8 | return when (this) { 9 | GROOVY -> "build.gradle" 10 | KOTLIN -> "build.gradle.kts" 11 | } 12 | } 13 | 14 | fun settingsFilename(): String { 15 | return when (this) { 16 | GROOVY -> "settings.gradle" 17 | KOTLIN -> "templates/settings.gradle.kts" 18 | } 19 | } 20 | 21 | fun mapAssociator(): String { 22 | return when (this) { 23 | GROOVY -> ":" 24 | KOTLIN -> "to" 25 | } 26 | } 27 | 28 | /** 29 | * Builds the string content of an extra property on a buildscript. 30 | */ 31 | fun extraPropertyString(propertyName: String, value: String): String { 32 | val writtenValue = "\"$value\"" 33 | return when (this) { 34 | GROOVY -> " $propertyName = $writtenValue" 35 | KOTLIN -> "val $propertyName: String by extra($writtenValue)" 36 | } 37 | } 38 | 39 | /** 40 | * Returns the string that resolves the sdk version to use for the Ignition sdk dependencies. Assumes 41 | * the version has been defined as an 'extra project property' with the key of `sdk_version`. 42 | */ 43 | fun artifactSdkVersion(): String { 44 | return when (this) { 45 | GROOVY -> "${'$'}sdk_version" 46 | KOTLIN -> "${'$'}{rootProject.extra[\"sdk_version\"]}" 47 | } 48 | } 49 | 50 | fun skipSigningConfig(enable: Boolean = true): String { 51 | return when (this) { 52 | KOTLIN -> "skipModlSigning.set($enable)" 53 | GROOVY -> "skipModlSigning = $enable" 54 | } 55 | } 56 | 57 | fun dependencyBlock(): String { 58 | val block = """ 59 | |dependencies { 60 | | // 61 | |} 62 | | 63 | """.trimMargin() 64 | 65 | // dependency config syntax is currently the same for groovy and kts 66 | return when (this) { 67 | GROOVY -> block 68 | KOTLIN -> block 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/kotlin/io/ia/ignition/module/generator/api/ProjectScope.kt: -------------------------------------------------------------------------------- 1 | package io.ia.ignition.module.generator.api 2 | 3 | enum class ProjectScope(val folderName: String) { 4 | CLIENT("client"), 5 | DESIGNER("designer"), 6 | GATEWAY("gateway"), 7 | COMMON("common"), 8 | // not an ignition scope, but represents the root build gradle project scope 9 | ROOT(""); 10 | 11 | companion object { 12 | 13 | /** 14 | * Returns a list of valid ProjectScope elements according the the string. Lower case letters treated as 15 | * upper case, and invalid scope characters in the String are ignored. Common scope is not represented in 16 | * the resulting list. Use [effectiveScopesFromShorthand] if needing the effective project scopes given a 17 | * shorthand string. 18 | */ 19 | fun scopesFromShorthand(scopes: String): List { 20 | return scopes.uppercase() 21 | .map { 22 | when (it) { 23 | 'C' -> CLIENT 24 | 'D' -> DESIGNER 25 | 'G' -> GATEWAY 26 | else -> null 27 | } 28 | } 29 | .filterNotNull() 30 | } 31 | 32 | /** 33 | * Returns a list of valid ProjectScope elements according the the string. Lower case letters treated as 34 | * upper case, and invalid scope characters in the String are ignored. Common scope *is* represented in 35 | * the resulting list if the list includes more than one scope value.. Use [scopesFromShorthand] if needing 36 | * the exact project scopes given a shorthand string. 37 | */ 38 | fun effectiveScopesFromShorthand(scopes: String): List { 39 | val asParsed = scopesFromShorthand(scopes) 40 | 41 | return if (asParsed.size > 1) listOf(COMMON) + asParsed else asParsed 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/kotlin/io/ia/ignition/module/generator/api/SourceFileType.kt: -------------------------------------------------------------------------------- 1 | package io.ia.ignition.module.generator.api 2 | 3 | enum class SourceFileType { 4 | JAVA, 5 | KOTLIN, 6 | PROPERTIES, 7 | TOML; 8 | 9 | fun sourceCodeFileExtension(): String { 10 | return when (this) { 11 | JAVA -> "java" 12 | KOTLIN -> "kt" 13 | TOML -> "toml" 14 | PROPERTIES -> "properties" 15 | } 16 | } 17 | 18 | fun commonName(): String { 19 | return when (this) { 20 | JAVA -> JAVA.name.lowercase() 21 | KOTLIN -> KOTLIN.name.lowercase() 22 | TOML -> TOML.name.lowercase() 23 | PROPERTIES -> "properties file" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/kotlin/io/ia/ignition/module/generator/api/TemplateMarker.kt: -------------------------------------------------------------------------------- 1 | package io.ia.ignition.module.generator.api 2 | 3 | import io.ia.ignition.module.generator.api.ProjectScope.CLIENT 4 | import io.ia.ignition.module.generator.api.ProjectScope.COMMON 5 | import io.ia.ignition.module.generator.api.ProjectScope.DESIGNER 6 | import io.ia.ignition.module.generator.api.ProjectScope.GATEWAY 7 | 8 | /** 9 | * Markers used in the various template files that are string replaced during assembly in order to create the 10 | * appropriately named project elements. 11 | * 12 | * The values are set during initialization of the [GeneratorContext], according to data derived from the 13 | * [GeneratorConfig] 14 | */ 15 | enum class TemplateMarker(val key: String) { 16 | 17 | /** 18 | * The 'plain text' name of the module as displayed in the gateway module configuration pages 19 | */ 20 | MODULE_NAME(""), 21 | 22 | /** 23 | * Name of the modl file that is created 24 | */ 25 | MODULE_FILENAME(""), 26 | 27 | /** 28 | * Value that is replaced by the 'valid class name' formatted name 29 | */ 30 | MODULE_CLASSNAME(""), 31 | 32 | /** 33 | * Value that is replace by the 'package root' value provided to the Config by the creator 34 | */ 35 | PACKAGE_ROOT(""), 36 | 37 | /** 38 | * Value that is replaced by the derived Module Id 39 | */ 40 | MODULE_ID(""), 41 | 42 | /** 43 | * Value that is replaced by the appropriate array of projects and scopes 44 | */ 45 | PROJECT_SCOPES(""), 46 | 47 | /** 48 | * Value that is replaced by the name of the root project, e.g. value for _rootProject.name = 'my-project'_ in the 49 | * settings.gradle. 50 | */ 51 | ROOT_PROJECT_NAME(""), 52 | 53 | /** 54 | * Value that is replaced by a list of comma-separated Strings that consist of each subproject that is 55 | * registered by the settings.gradle or settings.gradle.kts file (syntax as appropriate) 56 | */ 57 | SUBPROJECT_INCLUDES(""), 58 | 59 | /** 60 | * Value replaced by the appropriate hook class references for each scope. 61 | */ 62 | HOOK_CLASS_CONFIG(""), 63 | 64 | /** 65 | * Value at the head of the settings.gradle file, to allow injecting things into the first line of the file 66 | */ 67 | SETTINGS_HEADER("//"), 68 | 69 | /* 70 | * The plugin configuration to apply to the root project of the generated module. 71 | */ 72 | ROOT_PLUGIN_CONFIGURATION(""), 73 | 74 | /** 75 | * Value used in the `dependencies { }` block of client scoped subproject 76 | */ 77 | CLIENT_DEPENDENCIES("//"), 78 | 79 | /** 80 | * Value used in the `dependencies` block of a common scoped subproject 81 | */ 82 | COMMON_DEPENDENCIES("//"), 83 | 84 | /** 85 | * Value used in the `dependencies { }` block of gateway scoped subproject 86 | */ 87 | GATEWAY_DEPENDENCIES("//"), 88 | 89 | /** 90 | * Value used in the `dependencies { }` block of designer scoped subproject 91 | */ 92 | DESIGNER_DEPENDENCIES("//"), 93 | 94 | JAVA_TOOLING_CONFIG("//"), 95 | 96 | SKIP_SIGNING_CONFIG("//"), 97 | 98 | MODL_PLUGIN_VERSION(""), 99 | 100 | SDK_VERSION_PLACEHOLDER(""); 101 | 102 | fun keys(): List { 103 | return values().map { it.key } 104 | } 105 | 106 | override fun toString(): String { 107 | return key 108 | } 109 | 110 | companion object { 111 | fun dependencyKeyForScope(scope: ProjectScope): TemplateMarker? { 112 | return when (scope) { 113 | CLIENT -> TemplateMarker.CLIENT_DEPENDENCIES 114 | DESIGNER -> TemplateMarker.DESIGNER_DEPENDENCIES 115 | GATEWAY -> TemplateMarker.GATEWAY_DEPENDENCIES 116 | COMMON -> TemplateMarker.COMMON_DEPENDENCIES 117 | else -> null 118 | } 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/kotlin/io/ia/ignition/module/generator/api/buildFiles.kt: -------------------------------------------------------------------------------- 1 | package io.ia.ignition.module.generator.api 2 | 3 | enum class BuildFileType { 4 | BUILDSCRIPT, 5 | SETTINGS, 6 | PROPERTY, 7 | VERSION_CATALOG, 8 | MISC 9 | } 10 | 11 | /** 12 | * A BuildFile represents a single file that will be generated. 13 | */ 14 | interface BuildFile { 15 | 16 | /** 17 | * The path and complete filename (including extension) of file that would be created and filled with the results 18 | * of [renderContents]. The path should be relative to the project or subroject the file is being generated for. 19 | */ 20 | fun getLocalFilePath(): String 21 | 22 | /** 23 | * Scope of the build file 24 | */ 25 | fun getScope(): ProjectScope 26 | 27 | /** 28 | * Called to build the full string contents of the build file. 29 | */ 30 | fun renderContents(): String 31 | } 32 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/kotlin/io/ia/ignition/module/generator/data/ValidationResult.kt: -------------------------------------------------------------------------------- 1 | package io.ia.ignition.module.generator.data 2 | 3 | data class ValidationResult(val validated: Boolean, internal val message: String? = null) 4 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/kotlin/io/ia/ignition/module/generator/data/validation.kt: -------------------------------------------------------------------------------- 1 | package io.ia.ignition.module.generator.data 2 | 3 | import java.nio.file.Files 4 | import java.nio.file.Path 5 | import javax.lang.model.SourceVersion 6 | 7 | /** 8 | * Validates the name intended to be used as the human readable module name. 9 | * 10 | * This name should begin with a letter, and include nothing but spaces, letters and numbers. It should _not_ include 11 | * a "Module" suffix, and ideally is not excessively long (32 characters This name will undergo processing to initialize the values and names used in the generated stub 12 | * files in the new project. The default generated file/package names may always be modified after generation. 13 | * 14 | * Valid Names: 15 | * Web Connector 16 | * Bridge Perspective Component 17 | * SpecialPump Device Driver 18 | * Faster Playback 19 | * Smartchart10x 20 | * 21 | * Invalid Names: 22 | * 1st Character Is A Number 23 | * _Crafty Module_ 24 | * Started With A Space 25 | * Contains F&nky Illegal Characters 26 | */ 27 | fun validateModuleName(name: String): ValidationResult { 28 | return when { 29 | name.endsWith("Module") -> 30 | ValidationResult(false, "The module name $name ends with the suffix \"Module\".") 31 | !name.matches(Regex("^[a-zA-Z][a-zA-Z0-9 ]*")) -> 32 | ValidationResult(false, "The module name $name contains illegal characters or does not start with a letter.") 33 | else -> { 34 | var message = "The module name $name is valid." 35 | if (name.length > 32) { 36 | message += " The module name is excessively long, consider renaming." 37 | } 38 | ValidationResult(true, message) 39 | } 40 | } 41 | } 42 | 43 | /** 44 | * Validates the package path. Checks if the path is a valid qualified path 45 | */ 46 | fun validatePackagePath(packagePath: String): ValidationResult { 47 | return if (!SourceVersion.isName(packagePath)) { 48 | ValidationResult(false, "The package path $packagePath is not a valid path.") 49 | } else { 50 | ValidationResult(true, "The package path $packagePath is valid.") 51 | } 52 | } 53 | 54 | /** 55 | * Validates the parent directory path. 56 | * Checks whether the path exists, is a directory, and if the directory is writable. 57 | */ 58 | fun validateParentDirPath(path: Path): ValidationResult { 59 | return when { 60 | !Files.exists(path) -> ValidationResult(false, "The parent path $path does not exist.") 61 | !Files.isDirectory(path) -> ValidationResult(false, "The parent path $path is not a directory.") 62 | !Files.isWritable(path) -> ValidationResult(false, "The parent path $path is not writeable.") 63 | else -> ValidationResult(true, "The parent path $path is validated.") 64 | } 65 | } 66 | 67 | /** 68 | * Validates the provided scope string. Will return valid if the scope string contains 69 | * at least one valid scope or if the scope string contains valid duplicates 70 | * 71 | * Valid Scopes: 72 | * G 73 | * GC 74 | * GDC 75 | * GDDCC 76 | * ABC 77 | * 78 | * Invalid Scopes: 79 | * 123 80 | * XYZ 81 | * jkl 82 | * @param scope scope string: G - Gateway, D - Designer, C - Client 83 | * @return 84 | */ 85 | fun validateScope(scope: String): ValidationResult { 86 | val (valid, invalid) = scope.partition { it == 'G' || it == 'D' || it == 'C' } 87 | return if (valid.isNotEmpty()) { 88 | val message: String = StringBuilder().apply { 89 | append("We found ${ valid.toSet().joinToString() }") 90 | 91 | if (invalid.isNotEmpty()) { 92 | append(", but additional unrecognized characters were found and ignored in the scope parameter(s) ${ invalid.toSet().joinToString() }.") 93 | } 94 | }.toString() 95 | ValidationResult(true, message) 96 | } else { 97 | ValidationResult(false, "No valid scopes were found in $scope.") 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/kotlin/io/ia/ignition/module/generator/error/IllegalConfigurationException.kt: -------------------------------------------------------------------------------- 1 | package io.ia.ignition.module.generator.error 2 | 3 | import java.lang.IllegalArgumentException 4 | 5 | /** 6 | * Thrown when an improper configuration object is provided to the generator 7 | */ 8 | class IllegalConfigurationException(message: String) : IllegalArgumentException(message) 9 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/kotlin/io/ia/ignition/module/generator/util/generatorUtils.kt: -------------------------------------------------------------------------------- 1 | package io.ia.ignition.module.generator.util 2 | 3 | import io.ia.ignition.module.generator.api.GeneratorContext 4 | import io.ia.ignition.module.generator.api.GradleDsl 5 | import io.ia.ignition.module.generator.api.ProjectScope 6 | import io.ia.ignition.module.generator.api.SourceFileType 7 | import io.ia.ignition.module.generator.api.SourceFileType.JAVA 8 | import org.slf4j.Logger 9 | import org.slf4j.LoggerFactory 10 | import java.nio.file.Files 11 | import java.nio.file.Path 12 | import java.nio.file.Paths 13 | 14 | val logger: Logger = LoggerFactory.getLogger("generatorUtils") 15 | 16 | data class SubProjectSettings( 17 | val moduleRootDir: Path, 18 | val subprojectDir: Path, 19 | val packagePath: String, 20 | val buildscriptLanguage: GradleDsl, 21 | val projectLanguage: SourceFileType, 22 | val scope: ProjectScope, 23 | val dependencies: String = "" // optional dependencies injected into build file 24 | ) 25 | 26 | fun buildSubProjectSettings(context: GeneratorContext, scope: ProjectScope): SubProjectSettings { 27 | val moduleRootDir = context.getRootDirectory() 28 | val config = context.config 29 | val subProjectDir = if (config.scopes.length == 1 && config.useRootProjectWhenSingleScope) { 30 | // the 'root' project is the 'subproject dir' in a single project setup 31 | moduleRootDir.toAbsolutePath() 32 | } else { 33 | moduleRootDir.resolve(scope.folderName).toAbsolutePath() 34 | } 35 | 36 | val packagePath = context.config.packageName.toPackagePath(scope) 37 | 38 | return SubProjectSettings( 39 | moduleRootDir, subProjectDir, packagePath, context.config.buildDsl, 40 | context.config.projectLanguage, scope 41 | ) 42 | } 43 | 44 | /** 45 | * Creates a module subproject folder structure based on the subproject model, populating it with the appropriate 46 | * build.gradle file and hook files. If the scope is 'common', instead of generating a module hook, will 47 | * generate an empty "ModuleName.java" file. 48 | * 49 | * @param context context object containing a validated raw GeneratorConfig 50 | * @param config subproject configuration object 51 | */ 52 | fun createSubProject(context: GeneratorContext, config: SubProjectSettings): Path { 53 | val projectLanguage = context.config.projectLanguage 54 | val hookDir = createSourceDirs(config.subprojectDir, config.packagePath, projectLanguage) 55 | writeHookFile(hookDir, context, config.scope) 56 | 57 | // write the buildscript for this subproject 58 | val buildScriptTemplateResource = "templates/buildscript/${config.scope.folderName}.${context.getBuildScriptFilename()}" 59 | val buildScript = config.subprojectDir.resolve(context.getBuildScriptFilename()) 60 | 61 | buildScript.createAndFillFromResource(buildScriptTemplateResource, context.getTemplateReplacements()) 62 | 63 | return config.subprojectDir 64 | } 65 | 66 | /** 67 | * Writes the stub hook file for the given scope, creating appropriate package directories and updating the templates 68 | * with appropriate class and package names. 69 | * 70 | * @param hookDir the ABSOLUTE path to where a module Hook would be created 71 | * @param context context containing validated config 72 | * @param scope Ignition scope this hook is being created for 73 | */ 74 | fun writeHookFile(hookDir: Path, context: GeneratorContext, scope: ProjectScope) { 75 | val hookExtension = context.config.projectLanguage.sourceCodeFileExtension() 76 | val hookClassFileName = "${context.getHookClassName(scope)}.$hookExtension" 77 | val hookFile = hookDir.toAbsolutePath().resolve(hookClassFileName) 78 | val resourcePath = "templates/${context.getHookResourcePath(scope)}" 79 | hookFile.createAndFillFromResource(resourcePath, context.getTemplateReplacements()) 80 | } 81 | 82 | /** 83 | * Creates the source code directory structure for a conventional maven/gradle/java style project 84 | * @param parentDir the directory in which the source folders should be created, generally in a gradle project 85 | * directory 86 | * @param scopeTerminatedPackagePath the package path, with the final path element being the scope (if not a single 87 | * scope/mono-project) 88 | * @param language the language intended to be used for this project. DefaultDependencies to SourceFileType.JAVA 89 | * @return [Path] pointing to the src/main/package/name/scope source directory, where a hook file is commonly located 90 | */ 91 | fun createSourceDirs(parentDir: Path, scopeTerminatedPackagePath: String, language: SourceFileType = JAVA): Path { 92 | 93 | // make the main and test sourceset folders, e.g. /src/main 94 | val srcMain = Paths.get(parentDir.toAbsolutePath().toString(), "src", "main") 95 | val srcTest = Paths.get(parentDir.toAbsolutePath().toString(), "src", "test") 96 | 97 | // append the package path and scope (scope derived from parent directory name), e.g. /io/ia/thing/client 98 | val mainCode = srcMain.resolve(language.commonName()).resolve(scopeTerminatedPackagePath) 99 | val testCode = srcTest.resolve(language.commonName()).resolve(scopeTerminatedPackagePath) 100 | 101 | logger.debug("createSrcDirs().mainCode path is '$mainCode'") 102 | 103 | listOf(mainCode, testCode).forEach { 104 | if (!Files.exists(it)) { 105 | logger.debug("Creating: ${it.toAbsolutePath()}") 106 | Files.createDirectories(it) 107 | } else { 108 | logger.error("Could not create new source directory,dir already present at '${it.toFile().absolutePath}'") 109 | } 110 | } 111 | 112 | return mainCode 113 | } 114 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/gradle/wrapper/6_4_1/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inductiveautomation/ignition-module-tools/4fd236ab99605f165c610000eae27b3d13e60f92/generator/generator-core/src/main/resources/gradle/wrapper/6_4_1/gradle-wrapper.jar -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/gradle/wrapper/6_4_1/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.4.1-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/gradle/wrapper/6_4_1/scripts/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto init 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto init 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :init 68 | @rem Get command-line arguments, handling Windows variants 69 | 70 | if not "%OS%" == "Windows_NT" goto win9xME_args 71 | 72 | :win9xME_args 73 | @rem Slurp the command line arguments. 74 | set CMD_LINE_ARGS= 75 | set _SKIP=2 76 | 77 | :win9xME_args_slurp 78 | if "x%~1" == "x" goto execute 79 | 80 | set CMD_LINE_ARGS=%* 81 | 82 | :execute 83 | @rem Setup the command line 84 | 85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 86 | 87 | 88 | @rem Execute Gradle 89 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 90 | 91 | :end 92 | @rem End local scope for the variables with windows NT shell 93 | if "%ERRORLEVEL%"=="0" goto mainEnd 94 | 95 | :fail 96 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 97 | rem the _cmd.exe /c_ return code! 98 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 99 | exit /b 1 100 | 101 | :mainEnd 102 | if "%OS%"=="Windows_NT" endlocal 103 | 104 | :omega 105 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/gradle/wrapper/6_5_1/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inductiveautomation/ignition-module-tools/4fd236ab99605f165c610000eae27b3d13e60f92/generator/generator-core/src/main/resources/gradle/wrapper/6_5_1/gradle-wrapper.jar -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/gradle/wrapper/6_5_1/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/gradle/wrapper/6_5_1/scripts/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto init 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto init 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :init 68 | @rem Get command-line arguments, handling Windows variants 69 | 70 | if not "%OS%" == "Windows_NT" goto win9xME_args 71 | 72 | :win9xME_args 73 | @rem Slurp the command line arguments. 74 | set CMD_LINE_ARGS= 75 | set _SKIP=2 76 | 77 | :win9xME_args_slurp 78 | if "x%~1" == "x" goto execute 79 | 80 | set CMD_LINE_ARGS=%* 81 | 82 | :execute 83 | @rem Setup the command line 84 | 85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 86 | 87 | 88 | @rem Execute Gradle 89 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 90 | 91 | :end 92 | @rem End local scope for the variables with windows NT shell 93 | if "%ERRORLEVEL%"=="0" goto mainEnd 94 | 95 | :fail 96 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 97 | rem the _cmd.exe /c_ return code! 98 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 99 | exit /b 1 100 | 101 | :mainEnd 102 | if "%OS%"=="Windows_NT" endlocal 103 | 104 | :omega 105 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/gradle/wrapper/6_8_2/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inductiveautomation/ignition-module-tools/4fd236ab99605f165c610000eae27b3d13e60f92/generator/generator-core/src/main/resources/gradle/wrapper/6_8_2/gradle-wrapper.jar -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/gradle/wrapper/6_8_2/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.2-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/gradle/wrapper/6_8_2/scripts/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/gradle/wrapper/7_2/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inductiveautomation/ignition-module-tools/4fd236ab99605f165c610000eae27b3d13e60f92/generator/generator-core/src/main/resources/gradle/wrapper/7_2/gradle-wrapper.jar -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/gradle/wrapper/7_2/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/gradle/wrapper/7_2/scripts/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/gradle/wrapper/7_5_1/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inductiveautomation/ignition-module-tools/4fd236ab99605f165c610000eae27b3d13e60f92/generator/generator-core/src/main/resources/gradle/wrapper/7_5_1/gradle-wrapper.jar -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/gradle/wrapper/7_5_1/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/gradle/wrapper/7_5_1/scripts/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if %ERRORLEVEL% equ 0 goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if %ERRORLEVEL% equ 0 goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | set EXIT_CODE=%ERRORLEVEL% 84 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 85 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 86 | exit /b %EXIT_CODE% 87 | 88 | :mainEnd 89 | if "%OS%"=="Windows_NT" endlocal 90 | 91 | :omega 92 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/gradle/wrapper/7_6/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inductiveautomation/ignition-module-tools/4fd236ab99605f165c610000eae27b3d13e60f92/generator/generator-core/src/main/resources/gradle/wrapper/7_6/gradle-wrapper.jar -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/gradle/wrapper/7_6/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/gradle/wrapper/7_6/scripts/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/templates/buildscript/client.build.gradle: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | id "java-library" 4 | } 5 | 6 | java { 7 | toolchain { 8 | languageVersion.set(org.gradle.jvm.toolchain.JavaLanguageVersion.of(11)) 9 | } 10 | } 11 | 12 | dependencies { 13 | // 14 | // add client scoped dependencies here 15 | } 16 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/templates/buildscript/client.build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `java-library` 3 | } 4 | 5 | java { 6 | toolchain { 7 | languageVersion.set(org.gradle.jvm.toolchain.JavaLanguageVersion.of(11)) 8 | } 9 | } 10 | 11 | dependencies { 12 | // 13 | // add client scoped dependencies here 14 | } 15 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/templates/buildscript/common.build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "java-library" 3 | } 4 | 5 | java { 6 | toolchain { 7 | languageVersion.set(org.gradle.jvm.toolchain.JavaLanguageVersion.of(11)) 8 | } 9 | } 10 | 11 | dependencies { 12 | // add common scoped dependencies here 13 | } 14 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/templates/buildscript/common.build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `java-library` 3 | } 4 | 5 | java { 6 | toolchain { 7 | languageVersion.set(org.gradle.jvm.toolchain.JavaLanguageVersion.of(11)) 8 | } 9 | } 10 | 11 | dependencies { 12 | // add common scoped dependencies here 13 | // 14 | } 15 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/templates/buildscript/designer.build.gradle: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | id "java-library" 4 | } 5 | 6 | java { 7 | toolchain { 8 | languageVersion.set(org.gradle.jvm.toolchain.JavaLanguageVersion.of(11)) 9 | } 10 | } 11 | 12 | dependencies { 13 | // 14 | 15 | // add designer scoped dependencies here 16 | } 17 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/templates/buildscript/designer.build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `java-library` 3 | } 4 | 5 | java { 6 | toolchain { 7 | languageVersion.set(org.gradle.jvm.toolchain.JavaLanguageVersion.of(11)) 8 | } 9 | } 10 | 11 | dependencies { 12 | // 13 | 14 | // add designer scoped dependencies here 15 | } 16 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/templates/buildscript/gateway.build.gradle: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | id "java-library" 4 | } 5 | 6 | java { 7 | toolchain { 8 | languageVersion.set(JavaLanguageVersion.of(11)) 9 | } 10 | } 11 | 12 | dependencies { 13 | // 14 | 15 | // add gateway scoped dependencies here 16 | } 17 | 18 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/templates/buildscript/gateway.build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `java-library` 3 | } 4 | 5 | java { 6 | toolchain { 7 | languageVersion.set(org.gradle.jvm.toolchain.JavaLanguageVersion.of(11)) 8 | } 9 | } 10 | 11 | dependencies { 12 | // 13 | // add gateway scoped dependencies here 14 | } 15 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/templates/buildscript/root.build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * This project was generated by the Ignition Module Generator. It is intended to provide initial project structure 3 | * and minimize boilerplate. This initial project is a starting point for your own module projects using the Gradle 4 | * build system. It is not intended to be a replacement for learning and understanding Gradle. 5 | * 6 | * Generator: https://github.com/inductiveautomation/ignition-module-tools/tree/master/generator/generator-core 7 | * Gradle Docs: https://docs.gradle.org/current/userguide/userguide.html 8 | */ 9 | 10 | plugins { 11 | 12 | } 13 | 14 | ext { 15 | sdk_version = "8.1.20" 16 | } 17 | 18 | allprojects { 19 | version = "0.0.1-SNAPSHOT" 20 | } 21 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/templates/buildscript/root.build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * This project was generated by the Ignition Module Generator. It is intended to provide initial project structure 3 | * and minimize boilerplate. This initial project is a starting point for your own module projects using the Gradle 4 | * build system. It is not intended to be a replacement for learning and understanding Gradle. 5 | * 6 | * Generator: https://github.com/inductiveautomation/ignition-module-tools/tree/master/generator/generator-core 7 | * Gradle Docs: https://docs.gradle.org/current/userguide/userguide.html 8 | */ 9 | 10 | plugins { 11 | 12 | } 13 | 14 | val sdk_version by extra("8.1.20") 15 | 16 | allprojects { 17 | version = "0.0.1-SNAPSHOT" 18 | } 19 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/templates/config/modlPluginConfig.groovy: -------------------------------------------------------------------------------- 1 | 2 | ignitionModule { 3 | /* 4 | * Human readable name of the module, as will be displayed on the gateway status page 5 | */ 6 | name = "" 7 | 8 | /* 9 | * Name of the '.modl' file to be created, without file extension. 10 | */ 11 | fileName = "" 12 | /* 13 | * Unique identifier for the module. Reverse domain convention is recommended (e.g.: com.mycompany.charting-module) 14 | */ 15 | id = "" 16 | 17 | moduleVersion = version 18 | 19 | moduleDescription = "A short sentence describing what it does, but not much longer than this." 20 | /* 21 | * Minimum version of Ignition required for the module to function correctly. This typically won't change over 22 | * the course of a major Ignition (7.9, 8.0, etc) version, except for when the Ignition Platform adds/changes APIs 23 | * used by the module. 24 | */ 25 | requiredIgnitionVersion = rootProject.ext.sdk_version 26 | /* 27 | * This is a map of String: String, where the 'key' represents the fully qualified path to the project 28 | * (using gradle path syntax), and the value is the shorthand Scope string. 29 | * Example entry: [ ":gateway": "G", ":common": "GC", ":vision-client": "C" ] 30 | */ 31 | projectScopes = [ 32 | 33 | ] 34 | 35 | /* 36 | * Add your module dependencies here, following the examples, with scope being one or more of G, C or D, 37 | * for (G)ateway, (D)esigner, Vision (C)lient. 38 | * 39 | * Example Value: 40 | * moduleDependencies = [ 41 | * "com.inductiveautomation.vision": "CD", 42 | * "com.inductiveautomation.opcua": "G" 43 | * ] 44 | */ 45 | moduleDependencies = [ : ] 46 | 47 | /* 48 | * Add required module dependencies here, following the examples, with scope being one or more of G, C or D, 49 | * for (G)ateway, (D)esigner, Vision (C)lient. 50 | * 51 | * Example: 52 | * moduleDependencySpecs { 53 | * register("com.inductiveautomation.vision") { 54 | * scope = "GCD" 55 | * required = true 56 | * } 57 | * // register("com.another.mod") { ... 58 | * } 59 | * 60 | * If any of module's required module dependencies are not present, the 61 | * gateway will fault on loading the module. 62 | * 63 | * NOTE: For modules targeting Ignition 8.3 and later. Use `moduleDependencies` for 8.1 and earlier. 64 | * This property will only add the "required" flag if {requiredIgnitionVersion} is at least 8.3 65 | * 66 | */ 67 | moduleDependencySpecs { } 68 | 69 | /* 70 | * Map of fully qualified hook class to the shorthand scope. Only one scope per hook class. 71 | * 72 | * Example entry: "com.myorganization.vectorizer.VectorizerDesignerHook": "D" 73 | */ 74 | hooks = [ 75 | 76 | ] 77 | 78 | applyInductiveArtifactRepo = true 79 | 80 | /* 81 | * Optional 'documentation' settings. Supply the files that would be desired to end up in the 'doc' dir of the 82 | * assembled module, and specify the path to the index.html file inside that folder. In this commented-out 83 | * example, the html files being collected are located in the module root project in `src/docs/` 84 | */ 85 | // the files to collect into the documentation dir, with example implementation 86 | // documentationFiles.from(project.file("src/docs/")) 87 | 88 | // the path from the root documentation dir to the index file. 89 | // documentationIndex.set("index.html") 90 | 91 | /* 92 | * Optional unsigned modl settings. If true, modl signing will be skipped. This is not for production and should 93 | * be used merely for development testing 94 | */ 95 | // 96 | } 97 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/templates/config/modlPluginConfig.kts: -------------------------------------------------------------------------------- 1 | ignitionModule { 2 | /* 3 | * Human readable name of the module, as will be displayed on the gateway status page 4 | */ 5 | name.set("") 6 | 7 | /* 8 | * Name of the '.modl' file to be created, without file extension. 9 | */ 10 | fileName.set("") 11 | /* 12 | * Unique identifier for the module. Reverse domain convention is recommended (e.g.: com.mycompany.charting-module) 13 | */ 14 | id.set("") 15 | 16 | /* 17 | * Version of the module. Here being set to the same version that gradle uses, up above in this file. 18 | */ 19 | moduleVersion.set("${project.version}") 20 | 21 | moduleDescription.set("A short sentence describing what it does, but not much longer than this.") 22 | 23 | /* 24 | * Minimum version of Ignition required for the module to function correctly. This typically won't change over 25 | * the course of a major Ignition (7.9, 8.0, etc) version, except for when the Ignition Platform adds/changes APIs 26 | * used by the module. 27 | */ 28 | requiredIgnitionVersion.set(sdk_version) 29 | /* 30 | * This is a map of String: String, where the 'key' represents the fully qualified path to the project 31 | * (using gradle path syntax), and the value is the shorthand Scope string. 32 | * Example entry: listOf( ":gateway" to "G", ":common" to "GC", ":vision-client" to "C" ) 33 | */ 34 | projectScopes.putAll(mapOf( 35 | 36 | )) 37 | 38 | /* 39 | * Add your module dependencies here, following the examples, with scope being one or more of G, C or D, 40 | * for (G)ateway, (D)esigner, Vision (C)lient. 41 | * Example: 42 | * moduleDependencies = mapOf( 43 | * "CD" to "com.inductiveautomation.vision", 44 | * "G" to "com.inductiveautomation.opcua" 45 | * ) 46 | */ 47 | moduleDependencies.set(mapOf()) 48 | 49 | /* 50 | * Add required module dependencies here, following the examples, with scope being one or more of G, C or D, 51 | * for (G)ateway, (D)esigner, Vision (C)lient. 52 | * 53 | * Example: 54 | * moduleDependencySpecs { 55 | * register("com.inductiveautomation.vision") { 56 | * scope = "GCD" 57 | * required = true 58 | * } 59 | * // register("com.another.mod") { ... 60 | * } 61 | * 62 | * If any of module's required module dependencies are not present, the 63 | * gateway will fault on loading the module. 64 | * 65 | * NOTE: For modules targeting Ignition 8.3 and later. Use `moduleDependencies` for 8.1 and earlier. 66 | * This property will only add the "required" flag if {requiredIgnitionVersion} is at least 8.3 67 | * 68 | */ 69 | moduleDependencySpecs { } 70 | 71 | /* 72 | * Map of fully qualified hook class to the shorthand scope. Only one scope may apply to a class, and each scope 73 | * must have no more than single class registered. You may omit scope registrations if they do not apply. 74 | * 75 | * Example entry: "com.myorganization.vectorizer.VectorizerDesignerHook" to "D" 76 | */ 77 | hooks.putAll(mapOf( 78 | 79 | )) 80 | 81 | /* 82 | * Optional 'documentation' settings. Supply the files that would be desired to end up in the 'doc' dir of the 83 | * assembled module, and specify the path to the index.html file inside that folder. In this commented-out 84 | * example, the html files being collected are located in the module root project in `src/docs/` 85 | */ 86 | // the files to collect into the documentation dir, with example implementation 87 | // documentationFiles.from(project.file("src/docs/")) 88 | 89 | /* The path from the root documentation dir to the index file, or filename if in the root doc dir. */ 90 | // documentationIndex.set("index.html") 91 | 92 | /* 93 | * Optional unsigned modl settings. If true, modl signing will be skipped. This is not for production and should 94 | * be used merely for development testing 95 | */ 96 | // 97 | } 98 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/templates/hook/ClientHook.java: -------------------------------------------------------------------------------- 1 | package .client; 2 | 3 | import com.inductiveautomation.vision.api.client.AbstractClientModuleHook; 4 | 5 | /** 6 | * Client Hook for projects which target Vision 7 | * 8 | * @since 9 | */ 10 | public class ClientHook extends AbstractClientModuleHook { 11 | // override methods as required 12 | } 13 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/templates/hook/ClientHook.kt: -------------------------------------------------------------------------------- 1 | package .client 2 | 3 | import com.inductiveautomation.vision.api.client.AbstractClientModuleHook 4 | 5 | class ClientHook: AbstractClientModuleHook { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/templates/hook/DesignerHook.java: -------------------------------------------------------------------------------- 1 | package .designer; 2 | 3 | import com.inductiveautomation.ignition.common.licensing.LicenseState; 4 | import com.inductiveautomation.ignition.designer.model.AbstractDesignerModuleHook; 5 | import com.inductiveautomation.ignition.designer.model.DesignerContext; 6 | 7 | 8 | /** 9 | * This is the Designer-scope module hook. The minimal implementation contains a startup method. 10 | */ 11 | public class DesignerHook extends AbstractDesignerModuleHook { 12 | 13 | // override additonal methods as requried 14 | 15 | @Override 16 | public void startup(DesignerContext context, LicenseState activationState) throws Exception { 17 | // implelement functionality as required 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/templates/hook/DesignerHook.kt: -------------------------------------------------------------------------------- 1 | package .designer 2 | 3 | import com.inductiveautomation.ignition.common.licensing.LicenseState 4 | import com.inductiveautomation.ignition.designer.model.AbstractDesignerModuleHook 5 | import com.inductiveautomation.ignition.designer.model.DesignerContext 6 | 7 | 8 | /** 9 | * This is the Designer-scope module hook. The minimal implementation contains a startup method. 10 | */ 11 | class DesignerHook: AbstractDesignerModuleHook() { 12 | 13 | // override additonal methods as requried 14 | 15 | @Throws(Exception) 16 | override fun startup(context: DesignerContext, activationState: LicenseState) { 17 | // implement functionality as required 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/templates/hook/GatewayHook.kt: -------------------------------------------------------------------------------- 1 | package .gateway 2 | 3 | import com.inductiveautomation.ignition.common.expressions.ExpressionFunctionManager 4 | import com.inductiveautomation.ignition.common.licensing.LicenseState 5 | import com.inductiveautomation.ignition.common.script.ScriptManager 6 | import com.inductiveautomation.ignition.common.xmlserialization.deserialization.XMLDeserializer 7 | import com.inductiveautomation.ignition.common.xmlserialization.serialization.XMLSerializer 8 | import com.inductiveautomation.ignition.gateway.AbstractGatewayModuleHook 9 | import com.inductiveautomation.ignition.gateway.clientcomm.ClientReqSession 10 | import com.inductiveautomation.ignition.gateway.web.models.INamedTab 11 | 12 | /** 13 | * Gateway scoped hook implementation 14 | */ 15 | class GatewayHook: AbstractGatewayModuleHook() { 16 | 17 | 18 | fun getRPCHandler(session: ClientReqSession?, projectName: String?): Any? { 19 | return null 20 | } 21 | 22 | val homepagePanels: List? 23 | get() = null 24 | 25 | val statusPanels: List? 26 | get() = null 27 | 28 | fun configureDeserializer(deserializer: XMLDeserializer?) { 29 | 30 | } 31 | 32 | fun configureSerializer(serializer: XMLSerializer?) { 33 | 34 | } 35 | fun configureFunctionFactory(factory: ExpressionFunctionManager?) { 36 | 37 | } 38 | 39 | fun notifyLicenseStateChanged(licenseState: LicenseState?) { 40 | 41 | } 42 | 43 | fun initializeScriptManager(manager: ScriptManager?) { 44 | 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/templates/hook/Module.java: -------------------------------------------------------------------------------- 1 | package .common; 2 | 3 | public class Module { 4 | public static final String MODULE_ID = ""; 5 | 6 | } 7 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/templates/hook/Module.kt: -------------------------------------------------------------------------------- 1 | package .common 2 | 3 | class Module { 4 | 5 | companion object { 6 | const val ID: String = "" 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/templates/settings.gradle: -------------------------------------------------------------------------------- 1 | // 2 | 3 | rootProject.name = "" 4 | 5 | dependencyResolutionManagement { 6 | repositories { 7 | // enable resolving dependencies from the inductive automation artifact repository 8 | maven { 9 | url "https://nexus.inductiveautomation.com/repository/public" 10 | } 11 | mavenCentral() 12 | } 13 | } 14 | 15 | include( 16 | 17 | ) 18 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/templates/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | // 2 | 3 | rootProject.name = "" 4 | 5 | dependencyResolutionManagement { 6 | repositories { 7 | mavenLocal() 8 | maven { 9 | url = uri("https://nexus.inductiveautomation.com/repository/public") 10 | } 11 | } 12 | } 13 | 14 | include( 15 | 16 | ) 17 | -------------------------------------------------------------------------------- /generator/generator-core/src/main/resources/templates/version-catalog/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | # Library/Dependency Versions 3 | ignitionSdk = "" 4 | 5 | [libraries] 6 | # Dependencies referencable in buildscripts. Note that dashes are replaced by periods in the buildscript reference. 7 | # batik-swing -> libs.batik.swing 8 | ignition-alarmNotificationCommon = { module = "com.inductiveautomation.ignitionsdk:alarm-notification-common", version.ref = "ignitionSdk" } 9 | ignition-alarmNotificationDesigner = { module = "com.inductiveautomation.ignitionsdk:alarm-notification-designer", version.ref = "ignitionSdk" } 10 | ignition-alarmNotificationGatewayApi = { module = "com.inductiveautomation.ignitionsdk:alarm-notification-gateway-api", version.ref = "ignitionSdk" } 11 | ignition-clientApi = { module = "com.inductiveautomation.ignitionsdk:client-api", version.ref = "ignitionSdk" } 12 | ignition-clientLauncher = { module = "com.inductiveautomation.ignitionsdk:client-launcher", version.ref = "ignitionSdk" } 13 | ignition-common = { module = "com.inductiveautomation.ignitionsdk:ignition-common", version.ref = "ignitionSdk" } 14 | ignition-designerApi = { module = "com.inductiveautomation.ignitionsdk:designer-api", version.ref = "ignitionSdk" } 15 | ignition-designerLauncher = { module = "com.inductiveautomation.ignitionsdk:designer-launcher", version.ref = "ignitionSdk" } 16 | ignition-driverApi = { module = "com.inductiveautomation.ignitionsdk:driver-api", version.ref = "ignitionSdk" } 17 | ignition-gatewayApi = { module = "com.inductiveautomation.ignitionsdk:gateway-api", version.ref = "ignitionSdk" } 18 | ignition-perspectiveCommon = { module = "com.inductiveautomation.ignitionsdk:perspective-common", version.ref = "ignitionSdk" } 19 | ignition-perspectiveDesigner = { module = "com.inductiveautomation.ignitionsdk:perspective-designer", version.ref = "ignitionSdk" } 20 | ignition-perspectiveGateway = { module = "com.inductiveautomation.ignitionsdk:perspective-gateway", version.ref = "ignitionSdk" } 21 | ignition-reportingCommon = { module = "com.inductiveautomation.ignitionsdk:reporting-common", version.ref = "ignitionSdk" } 22 | ignition-reportingDesigner = { module = "com.inductiveautomation.ignitionsdk:reporting-designer", version.ref = "ignitionSdk" } 23 | ignition-reportingGateway = { module = "com.inductiveautomation.ignitionsdk:reporting-gateway", version.ref = "ignitionSdk" } 24 | ignition-sfcClient = { module = "com.inductiveautomation.ignitionsdk:sfc-client", version.ref = "ignitionSdk" } 25 | ignition-sfcCommon = { module = "com.inductiveautomation.ignitionsdk:sfc-common", version.ref = "ignitionSdk" } 26 | ignition-sfcDesigner = { module = "com.inductiveautomation.ignitionsdk:sfc-designer", version.ref = "ignitionSdk" } 27 | ignition-sfcGatewayApi = { module = "com.inductiveautomation.ignitionsdk:sfc-gateway-api", version.ref = "ignitionSdk" } 28 | ignition-tagHistorian = { module = "com.inductiveautomation.ignitionsdk:tag-historian", version.ref = "ignitionSdk" } 29 | ignition-visionClientApi = { module = "com.inductiveautomation.ignitionsdk:vision-client-api", version.ref = "ignitionSdk" } 30 | ignition-visionCommon = { module = "com.inductiveautomation.ignitionsdk:vision-common", version.ref = "ignitionSdk" } 31 | ignition-visionDesignerApi = { module = "com.inductiveautomation.ignitionsdk:vision-designer-api", version.ref = "ignitionSdk" } 32 | -------------------------------------------------------------------------------- /generator/generator-core/src/test/java/io/ia/ignition/module/generator/ModuleGeneratorJavaTest.java: -------------------------------------------------------------------------------- 1 | package io.ia.ignition.module.generator; 2 | 3 | import java.nio.file.Path; 4 | 5 | import io.ia.ignition.module.generator.api.GeneratorConfig; 6 | import io.ia.ignition.module.generator.api.GeneratorConfigBuilder; 7 | import org.junit.Rule; 8 | import org.junit.Test; 9 | import org.junit.rules.TemporaryFolder; 10 | 11 | import static org.junit.Assert.assertNotNull; 12 | import static org.junit.Assert.assertNull; 13 | 14 | public class ModuleGeneratorJavaTest { 15 | @Rule 16 | public TemporaryFolder tempFolder = new TemporaryFolder(); 17 | 18 | @Test 19 | public void generatorSucceedsInJavaWithValidConfig() throws Exception { 20 | final String scope = "G"; 21 | final Path parentDir = tempFolder.newFolder().toPath(); 22 | final String moduleName = "Custom Thing"; 23 | final String packageName = "le.examp"; 24 | 25 | GeneratorConfig config = new GeneratorConfigBuilder() 26 | .scopes(scope) 27 | .parentDir(parentDir) 28 | .packageName(packageName) 29 | .moduleName(moduleName) 30 | .build(); 31 | 32 | Throwable t = null; 33 | 34 | try { 35 | ModuleGenerator.generate(config); 36 | } catch (Exception e) { 37 | t = e; 38 | } 39 | 40 | assertNull(t); 41 | } 42 | 43 | @Test 44 | public void generatorFailsInJavaWithInvalidConfig() { 45 | Throwable t = null; 46 | try { 47 | ModuleGenerator.generate(null); 48 | } catch (Exception e) { 49 | t = e; 50 | } 51 | 52 | assertNotNull(t); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /generator/generator-core/src/test/kotlin/io/ia/ignition/module/generator/api/ProjectScopeTest.kt: -------------------------------------------------------------------------------- 1 | package io.ia.ignition.module.generator.api 2 | 3 | import io.ia.ignition.module.generator.api.ProjectScope.CLIENT 4 | import io.ia.ignition.module.generator.api.ProjectScope.DESIGNER 5 | import io.ia.ignition.module.generator.api.ProjectScope.GATEWAY 6 | import org.junit.Assert.assertEquals 7 | import org.junit.Assert.assertTrue 8 | import org.junit.Test 9 | 10 | class ProjectScopeTest { 11 | 12 | @Test 13 | fun `valid scope shorthand string creates appropriate list of ProjectScopes`() { 14 | val cases = mapOf( 15 | "C" to listOf(CLIENT), 16 | "D" to listOf(DESIGNER), 17 | "G" to listOf(GATEWAY), 18 | "CD" to listOf(DESIGNER, CLIENT), 19 | "GCD" to listOf(GATEWAY, CLIENT, DESIGNER), 20 | "gcd" to listOf(GATEWAY, CLIENT, DESIGNER), 21 | "abc" to listOf(CLIENT), 22 | "xyz" to emptyList(), 23 | "GC" to listOf(GATEWAY, CLIENT), 24 | "GD" to listOf(GATEWAY, DESIGNER) 25 | ) 26 | 27 | cases.keys.forEach { 28 | val scopes = ProjectScope.scopesFromShorthand(it) 29 | 30 | assertEquals(scopes.sorted(), cases[it]?.sorted()) 31 | } 32 | } 33 | 34 | @Test 35 | fun `invalid scope shorthand string returns empty list`() { 36 | val cases = listOf("ZYX", "J", "12", "perry", "perspektive", "'", "") 37 | 38 | val emptyList = emptyList() 39 | 40 | cases.forEach { 41 | val scopes = ProjectScope.scopesFromShorthand(it) 42 | 43 | assertTrue(emptyList == scopes) 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /generator/generator-core/src/test/kotlin/io/ia/ignition/module/generator/data/GeneratorConfigTest.kt: -------------------------------------------------------------------------------- 1 | package io.ia.ignition.module.generator.data 2 | 3 | import io.ia.ignition.module.generator.api.GeneratorConfigBuilder 4 | import org.junit.Rule 5 | import org.junit.Test 6 | import org.junit.rules.TemporaryFolder 7 | import kotlin.test.assertTrue 8 | 9 | class GeneratorConfigTest { 10 | @get:Rule 11 | val tempFolder = TemporaryFolder() 12 | 13 | companion object { 14 | val scopes = "AAAABBBBB" 15 | val packageName = "le.examp" 16 | val moduleName = "My Test Module" 17 | } 18 | 19 | @Test 20 | fun `builder generates valid config object`() { 21 | 22 | val parentDir = tempFolder.newFolder() 23 | 24 | val config1 = GeneratorConfigBuilder() 25 | .moduleName(moduleName) 26 | .packageName(packageName) 27 | .parentDir(parentDir.toPath()) 28 | .scopes(scopes) 29 | .build() 30 | 31 | val config2 = GeneratorConfigBuilder() 32 | .moduleName(moduleName) 33 | .packageName(packageName) 34 | .parentDir(parentDir.toPath()) 35 | .scopes(scopes) 36 | .build() 37 | 38 | assertTrue((config1 == config2), "config objects built with same values should pass equality") 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /generator/generator-core/src/test/kotlin/io/ia/ignition/module/generator/data/GeneratorContextTest.kt: -------------------------------------------------------------------------------- 1 | package io.ia.ignition.module.generator.data 2 | 3 | import io.ia.ignition.module.generator.api.GeneratorConfigBuilder 4 | import io.ia.ignition.module.generator.api.GeneratorContext 5 | import io.ia.ignition.module.generator.api.GradleDsl 6 | import org.junit.Test 7 | import java.nio.file.Paths 8 | import kotlin.test.assertNotNull 9 | 10 | class GeneratorContextTest { 11 | 12 | @Test 13 | fun `context created successfully from correct settings object`() { 14 | val settings = GeneratorConfigBuilder() 15 | .settingsDsl(GradleDsl.GROOVY) 16 | .moduleName("Skynet Launcher") 17 | .packageName("bot.terminator.launcher") 18 | .parentDir(Paths.get(System.getProperty("user.home"))) 19 | .scopes("G") 20 | .build() 21 | 22 | val context: GeneratorContext = ModuleGeneratorContext(settings) 23 | 24 | assertNotNull(context, "GeneratorContext should not have been null!") 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /generator/generator-core/src/test/kotlin/io/ia/ignition/module/generator/data/ValidationTest.kt: -------------------------------------------------------------------------------- 1 | package io.ia.ignition.module.generator.data 2 | 3 | import org.junit.Test 4 | import kotlin.test.assertEquals 5 | 6 | class ValidationTest { 7 | 8 | companion object { 9 | val validNames = listOf( 10 | "Some Name", 11 | "Great Service Provider", 12 | "OMRON", 13 | "Great SCOTT", 14 | "awesome functionality" 15 | ) 16 | } 17 | 18 | @Test 19 | fun `valid module names pass validation`() { 20 | 21 | validNames.forEach { 22 | assertEquals( 23 | ValidationResult(true, "The module name $it is valid."), 24 | validateModuleName(it) 25 | ) 26 | } 27 | } 28 | 29 | @Test 30 | fun `valid module name long`() { 31 | val moduleName = "Some REEEEEAAALLLLLLLLLLLY LOOOOOOOOOOOOOOOONG MODULE Name" 32 | assertEquals( 33 | ValidationResult( 34 | true, 35 | "The module name $moduleName is valid. The module name is excessively long," + 36 | " consider renaming." 37 | ), 38 | validateModuleName(moduleName) 39 | ) 40 | } 41 | 42 | @Test 43 | fun `invalid module name suffix`() { 44 | val moduleName = "Bad Module" 45 | assertEquals( 46 | ValidationResult(false, "The module name Bad Module ends with the suffix \"Module\"."), 47 | validateModuleName(moduleName) 48 | ) 49 | } 50 | 51 | @Test 52 | fun `invalid module name invalid characters`() { 53 | val moduleName = "_Crafty Module_" 54 | assertEquals( 55 | ValidationResult( 56 | false, 57 | "The module name $moduleName contains illegal characters or does not start with a letter." 58 | ), 59 | validateModuleName(moduleName) 60 | ) 61 | } 62 | 63 | @Test 64 | fun `invalid module name not starting with letter`() { 65 | val moduleName = "1st Component" 66 | assertEquals( 67 | ValidationResult( 68 | false, 69 | "The module name $moduleName contains illegal characters or does not start with a letter." 70 | ), 71 | validateModuleName(moduleName) 72 | ) 73 | } 74 | 75 | @Test 76 | fun `valid package path`() { 77 | val packagePath = "my.awesome.module" 78 | assertEquals( 79 | ValidationResult(true, "The package path $packagePath is valid."), 80 | validatePackagePath(packagePath) 81 | ) 82 | } 83 | 84 | @Test 85 | fun `invalid package path`() { 86 | listOf( 87 | "contains.keyword.int.true.path", 88 | "contains@bad-separators", 89 | "too...many..separators" 90 | ).forEach { 91 | assertEquals( 92 | ValidationResult(false, "The package path $it is not a valid path."), 93 | validatePackagePath(it) 94 | ) 95 | } 96 | } 97 | 98 | @Test 99 | fun `valid scope`() { 100 | val scope = "GCD" 101 | 102 | assertEquals( 103 | ValidationResult(true, "We found G, C, D"), 104 | validateScope(scope) 105 | ) 106 | } 107 | 108 | @Test 109 | fun `valid scope with additional invalid scopes`() { 110 | val scope = "GCDAB" 111 | 112 | assertEquals( 113 | ValidationResult(true, "We found G, C, D, but additional unrecognized characters were found and ignored in the scope parameter(s) A, B."), 114 | validateScope(scope) 115 | ) 116 | } 117 | 118 | @Test 119 | fun `invalid scope values`() { 120 | 121 | listOf("_XYZ", "", "123", "#", "{}").forEach { 122 | assertEquals( 123 | ValidationResult(false, "No valid scopes were found in $it."), 124 | validateScope(it) 125 | ) 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /generator/generator-core/src/test/kotlin/io/ia/ignition/module/generator/util/GeneratorUtilsTest.kt: -------------------------------------------------------------------------------- 1 | package io.ia.ignition.module.generator.util 2 | 3 | import io.ia.ignition.module.generator.api.ProjectScope 4 | import io.ia.ignition.module.generator.api.SourceFileType.JAVA 5 | import org.junit.Rule 6 | import org.junit.Test 7 | import org.junit.rules.TemporaryFolder 8 | import org.slf4j.Logger 9 | import org.slf4j.LoggerFactory 10 | import java.io.File 11 | import kotlin.test.assertEquals 12 | import kotlin.test.assertTrue 13 | 14 | class GeneratorUtilsTest { 15 | private val logger: Logger = LoggerFactory.getLogger(GeneratorUtilsTest::class.java) 16 | 17 | @get:Rule 18 | val tempFolder = TemporaryFolder() 19 | 20 | @Test 21 | fun `creates valid subproject dirs`() { 22 | val rootTestDir = tempFolder.newFolder().absoluteFile.toPath() 23 | val testPackagePath = "com/inductiveautomation/example/client" 24 | 25 | // check tests for valid structure and content 26 | ProjectScope.values().forEach { 27 | 28 | val subProjectRootDir = rootTestDir.resolve(it.folderName) 29 | val created = createSourceDirs(subProjectRootDir, testPackagePath, JAVA) 30 | 31 | // what we want to have created 32 | val expectedPackagePath = "src/main/${JAVA.commonName()}/$testPackagePath/" 33 | val testFolder = File(subProjectRootDir.toFile(), expectedPackagePath) 34 | 35 | assertTrue(created.toFile().exists()) 36 | assertEquals(testFolder.absolutePath, created.toString()) 37 | } 38 | } 39 | 40 | @Test 41 | fun `class name is correctly built from valid module name`() { 42 | val validModuleNamesToExpectedClassFormat = mapOf( 43 | "Some Functionality" to "SomeFunctionality", 44 | "some functionality" to "SomeFunctionality", 45 | "Do The 123 Things" to "DoThe123Things", 46 | "I do cool stuff" to "IDoCoolStuff", 47 | "More Test Strings" to "MoreTestStrings" 48 | ) 49 | 50 | logger.debug("Executing ") 51 | validModuleNamesToExpectedClassFormat.forEach { 52 | 53 | val generatedClassName = it.key.toClassFriendlyName() 54 | logger.debug("Generated classname for ${it.key} was $generatedClassName") 55 | 56 | assertEquals( 57 | generatedClassName, 58 | it.value, 59 | "Generated classname from module name matched expected value" 60 | ) 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /generator/generator-core/src/test/resources/appendedResource.txt: -------------------------------------------------------------------------------- 1 | i am some appended text content 2 | -------------------------------------------------------------------------------- /generator/generator-core/src/test/resources/deeper/deeperResource.txt: -------------------------------------------------------------------------------- 1 | everything is dark in the deepest depths 2 | -------------------------------------------------------------------------------- /generator/generator-core/src/test/resources/fillFromResourceTest.txt: -------------------------------------------------------------------------------- 1 | Sittin' in the morning sun 2 | I'll be sittin' when the evening comes 3 | Watching the ships roll in 4 | Then I watch them roll away again, yeah 5 | 6 | I'm sittin' on the dock of the bay 7 | Watchin' the tide roll away, ooh 8 | I'm just sittin' on the dock of the bay 9 | Wastin' time 10 | 11 | I left my home in Georgia 12 | Headed for the Frisco Bay 13 | Cuz I've had nothing to live for 14 | And look like nothing's gonna come my way 15 | 16 | So, I'm just gon' sit on the dock of the bay 17 | Watchin' the tide roll away, ooh 18 | I'm sittin' on the dock of the bay 19 | Wastin' time 20 | 21 | Looks like nothing's gonna change 22 | Everything still remains the same 23 | I can't do what ten people tell me to do 24 | So I guess I'll remain the same, listen 25 | 26 | Sittin' here resting my bones 27 | And this loneliness won't leave me alone, listen 28 | Two thousand miles I roam 29 | Just to make this dock my home, now 30 | 31 | I'm just gon' sit at the dock of a bay 32 | Watchin' the tide roll away, ooh 33 | Sittin' on the dock of the bay 34 | Wastin' time 35 | -------------------------------------------------------------------------------- /generator/gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | kotlin = "1.7.10" 3 | picoCli = "4.6.3" 4 | slf4j = "1.7.36" 5 | 6 | [libraries] 7 | # Dependencies referencable in buildscripts. Note that dashes are replaced by periods in the buildscript reference. 8 | commonsCli = { module = "commons-cli:commons-cli", version = "1.4" } 9 | kotlinTest = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } 10 | kotlinTestJunit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } 11 | picoCli = { module = "info.picocli:picocli", version.ref = "picoCli"} 12 | picoCliCodegen = { module = "info.picocli:picocli-codegen", version.ref = "picoCli"} 13 | slf4jApi = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } 14 | slf4jSimple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" } 15 | 16 | 17 | [bundles] 18 | kotlinTest = ["kotlinTest", "kotlinTestJunit"] 19 | #slf4j 20 | #junit-jupiter = ["junit-jupiter-api", "junit-jupiter-engine"] 21 | #poi = ["apache-poi-core", "apache-poi-ooxml", "apache-poiooxml-schemas"] 22 | #xalans = ["xalan", "xalan-serializer"] 23 | #xml-api = ["xml-apis", "xml-apis-ext"] 24 | -------------------------------------------------------------------------------- /generator/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inductiveautomation/ignition-module-tools/4fd236ab99605f165c610000eae27b3d13e60f92/generator/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /generator/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /generator/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /generator/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | pluginManagement { 3 | // includeBuild("../gradle-module-plugin") 4 | } 5 | 6 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 7 | 8 | rootProject.name = "ignition-module-generator" 9 | 10 | // includeBuild("../gradle-module-plugin") 11 | 12 | include( 13 | ":", 14 | ":generator-core", 15 | ":generator-cli" 16 | ) 17 | -------------------------------------------------------------------------------- /gradle-module-plugin/Contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We welcome outside contributions to this library, and encourage feature requests/contributions through Github 4 | Issues in this repository. 5 | 6 | This is to help prevent duplicate work from occurring when contributing to this repository, please first discuss the 7 | change you wish to make with the project maintainers via issue or the IA forums. . 8 | 9 | ## Pull Request Process 10 | 11 | 1. Ensure any local files or dependencies are removed before pushing 12 | 2. Verify that new functionality is supported through new test cases when applicable 13 | 3. Update the README.md with details of changes to tasks, interfaces or behavior, including new environment 14 | variables, properties, useful file locations, etc. 15 | 4. Make sure that the changes in the PR conform to the code formatting by running `./gradlew check` in the appropriate project 16 | 17 | 18 | -------------------------------------------------------------------------------- /gradle-module-plugin/SIGNING_VIA_HSM.md: -------------------------------------------------------------------------------- 1 | *Signing modules via PKCS#11/HSM keystores is an incubating feature. See the note about the `certFile` setting below. The initial implementation was validated against a YubiKey 5 NFC, using the `9a` (PIV Authentication) instead of the `9c` (Digital Signature) slot. This is a workaround to the [repeated PIN challenge on every signing operation inherent to that slot](https://forum.inductiveautomation.com/t/ckr-user-not-logged-in-error-when-signing-zip-file-with-sectigo-usb-etoken/58107). In a future release this plugin should be able to support slot `9c`.* 2 | 3 | # Signing Via Hardware Security Module 4 | It's possible to sign a module using a PKCS#11 keystore, which is usually a hardware token such as a YubiKey. (There are software PKCS#11 keystores as well, though they are more common for testing in that they do not have the physical attributes that make for secure HSMs.) 5 | 6 | This is an alternative to PKCS#12 file-base keystores that typically reside in the subdirectory of a user's home directory. 7 | 8 | ## Prerequesites 9 | You should have the following: 10 | 11 | * A hardware (or software) PKCS#11 compliant keystore that supports the [Java `SunPKCS11` provider](https://docs.oracle.com/en/java/javase/17/security/pkcs11-reference-guide1.html#GUID-6DA72F34-6C6A-4F7D-ADBA-5811576A9331). 12 | * A PKCS#11 driver (or module) locally on your build workstation or on a build server, depending on the use case. Your HSM may be compatible with the [OpenSC Minidriver](https://github.com/OpenSC/OpenSC/wiki), which supports a wide variety of hardware tokens. Note that you may not get full support for `SunPKCS11` provider unless you use a driver from the HSM vendor. For example, YubiKey provides the [YKCS11 driver](https://developers.yubico.com/yubico-piv-tool/YKCS11/) as part of its `yubico-piv-tool` installation. 13 | * The `SunPKCS11` provider requires you have a PKCS#11 configuration file specifying, among other things, the location of that driver on your filesystem. You can see examples of such files for [YubiKeys on Windows](gradle-module-plugin/src/functionalTest/resources/certs/pkcs11-yk5-win.cfg) and for [OpenSC on Linux](gradle-module-plugin/src/functionalTest/resources/certs/pkcs11.cfg) in this repository. 14 | * Unless your HSM already contains your signing key(s), you may need a management application such as `yubico-piv-tool` or YubiKey Manager to generate a keypair on your device. You may also be able to use lower-level tools like `pkcs11-tool` that is bundled with the OpenSC tool suite or `keytool` that comes with Java. 15 | 16 | ## Steps 17 | The first few steps here are heavily dependent on your HSM's support of generic key management tools like OpenSC `pkcs11-tool` or Java `keytool`, or alternatively the vendor's key management tool. 18 | 19 | It is often the case however that even if you cannot generate (or import) a keypair with the generic tools, you may be able to list key information from the HSM with those tools. 20 | 21 | 1. Generate or import a SHA256 with RSA-type keypair using either the generic or vendor-specific key management tool. (Currently, the `module-signer` library called by the plugin looks [only for private keys with the `SHA256withRSA` algorithm type](https://github.com/inductiveautomation/module-signer/blob/master/src/main/java/com/inductiveautomation/ignitionsdk/ModuleSigner.java/#L95). Support for different key algorithms is in the planning stage.) 22 | 2. If you do not already have a cert file for the key on the filesystem, use the key management to export it from the keystore onto the filesystem. For purposes of module signing, the cert can be self-signed. It need not come from a Certificate Authority. In the future the plugin may be able to retrieve the cert from the HSM directly. 23 | 3. Retrieve the key alias using one of the key management tools, ideally via `keytool -list`. Note that your HSM may contain multiple private keys even if you only generated or imported a single signing key in step 1. With some HSMs cases you may be able to specify the key alias yourself during generation or import, and in other cases the HSM or the vendor key manager may hardcode it. Either way you need to note that alias for the next step. 24 | 4. Whether in a `gradle.properties` file or via command flags, sign your module as follows. We'll use command flags for clarity. Note how we do *not* pass `keystoreFile` as the private signing key is in the HSM. However PKCS#11 keystores almost universally require a PIN to "unlock" them, so `keystorePassword` is still required. 25 | 26 | ```bash 27 | $ gradlew :signModule \ 28 | --certAlias modsigning \ 29 | --certFile ~/.ssl/modsigning.cert \ 30 | --certPassword $CERT_PASSWORD_OR_PIN \ 31 | --keystorePassword $KS_PASSWORD_OR_PIN \ 32 | --pkcs11CfgFile $PATH_OF_PKCS11_CFG_FILE 33 | ``` 34 | -------------------------------------------------------------------------------- /gradle-module-plugin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.config.KotlinCompilerVersion 2 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 3 | import java.lang.Boolean.getBoolean 4 | import java.time.LocalDateTime 5 | 6 | plugins { 7 | `java-gradle-plugin` 8 | `maven-publish` 9 | id("com.gradle.plugin-publish") version "0.16.0" 10 | kotlin("jvm") version "1.7.10" 11 | id("com.diffplug.spotless") version "6.11.0" 12 | } 13 | 14 | repositories { 15 | mavenLocal() 16 | maven { 17 | url = uri("https://nexus.inductiveautomation.com/repository/public") 18 | } 19 | mavenCentral() 20 | } 21 | 22 | group = "io.ia.sdk" 23 | version = "0.4.1" 24 | 25 | configurations { 26 | val functionalTestImplementation by registering { 27 | extendsFrom(configurations["testImplementation"]) 28 | } 29 | } 30 | 31 | sourceSets { 32 | create("functionalTest") { 33 | compileClasspath += sourceSets.main.get().output + sourceSets.test.get().output + configurations.testCompileClasspath 34 | runtimeClasspath += compileClasspath + configurations.testRuntimeClasspath 35 | } 36 | } 37 | 38 | // Add a task to run the functional tests 39 | val functionalTest by tasks.registering(Test::class) { 40 | testClassesDirs = sourceSets["functionalTest"].output 41 | classpath += sourceSets["functionalTest"].runtimeClasspath 42 | 43 | group = "verification" 44 | description = "Executes tests in the 'functionalTest' sourceset" 45 | 46 | // because this is a custom task or some other Gradle voodoo we can not rely on the debug system prop 47 | // propagating to the GradleRunner in our function tests even though documentation suggests it 48 | // should; we have to wire this up explicitly 49 | val testkitDebugProp = "org.gradle.testkit.debug" // the unreferenceable (?) DefaultGradleRunner.DEBUG_SYS_PROP 50 | systemProperty(testkitDebugProp, getBoolean(testkitDebugProp)) 51 | } 52 | 53 | dependencies { 54 | // Align versions of all Kotlin components 55 | // Use the Kotlin JDK standard library. 56 | implementation(kotlin("bom", KotlinCompilerVersion.VERSION)) 57 | api(kotlin("stdlib-jdk8", KotlinCompilerVersion.VERSION)) 58 | api(kotlin("reflect", KotlinCompilerVersion.VERSION)) 59 | implementation(libs.guava) 60 | implementation(libs.moshi) 61 | implementation(libs.kotlinXmlBuilder) 62 | api(libs.moduleSigner) 63 | testImplementation(libs.kotlinTestJunit) 64 | testImplementation("io.ia.sdk.tools.module.gen:generator-core") 65 | } 66 | 67 | repositories { 68 | mavenCentral() 69 | } 70 | 71 | java { 72 | withJavadocJar() 73 | withSourcesJar() 74 | toolchain { 75 | this.languageVersion.set(JavaLanguageVersion.of(11)) 76 | } 77 | } 78 | 79 | kotlin { 80 | jvmToolchain { 81 | (this as JavaToolchainSpec).languageVersion.set(JavaLanguageVersion.of(11)) 82 | } 83 | } 84 | 85 | 86 | gradlePlugin { 87 | plugins { 88 | create("modl") { 89 | id = "io.ia.sdk.modl" 90 | implementationClass = "io.ia.sdk.gradle.modl.IgnitionModlPlugin" 91 | } 92 | } 93 | 94 | testSourceSets(sourceSets["functionalTest"], sourceSets["test"]) 95 | } 96 | 97 | pluginBundle { 98 | website = "https://www.github.com/inductiveautomation/ignition-module-tools" 99 | vcsUrl = "https://github.com/inductiveautomation/ignition-module-tools" 100 | description = "Create Modules to add capabilities to Inductive Automation's Ignition platform." 101 | plugins { 102 | named("modl") { 103 | displayName = "Ignition Module Builder Plugin" 104 | tags = setOf("inductiveautomation", "inductive automation", "ignition", "module", "modl", "maker", "iiot") 105 | version = "${project.version}" 106 | } 107 | } 108 | 109 | mavenCoordinates { 110 | // coordinates are automatically established as `gradle.plugin.${project.group}` when publishing to 111 | // gradle plugin portal, so no need to set this here. 112 | // groupId = project.getGroup() 113 | 114 | artifactId = "gradle-module-plugin" 115 | version = "${project.version}" 116 | } 117 | } 118 | 119 | tasks { 120 | withType(JavaCompile::class) { 121 | options.encoding = "UTF-8" 122 | } 123 | 124 | withType() { 125 | kotlinOptions { 126 | // will retain parameter names for java reflection 127 | javaParameters = true 128 | } 129 | } 130 | 131 | check { 132 | dependsOn(functionalTest) 133 | } 134 | 135 | test { 136 | dependsOn(functionalTest) 137 | } 138 | 139 | jar { 140 | manifest { 141 | attributes.putAll( 142 | mapOf( 143 | "Implementation-Title" to project.name, 144 | "Implementation-Version" to project.version, 145 | "Built-By" to System.getProperty("user.name"), 146 | "Built-JDK" to System.getProperty("java.version"), 147 | "Built-Gradle" to gradle.gradleVersion, 148 | "Build-Time" to LocalDateTime.now().toString() 149 | ) 150 | ) 151 | } 152 | } 153 | 154 | wrapper { 155 | distributionType = Wrapper.DistributionType.ALL 156 | } 157 | } 158 | 159 | spotless { 160 | kotlin { 161 | ktlint("0.44.0").editorConfigOverride(mapOf( 162 | "ktlint_disabled_rules" to "filename" 163 | )) 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /gradle-module-plugin/gradle.properties: -------------------------------------------------------------------------------- 1 | # If you need to debug during unit testing, flip this to 'true'. But beware. [1] 2 | systemProp.org.gradle.testkit.debug=false 3 | 4 | # [1] Historically the GradleRunner in io.ia.sdk.gradle.modl.BaseTest was hardcoded .withDebug(true). Running in debug 5 | # mode via this or via the property above can cause the following. 6 | # 7 | # 1. Significantly slower test execution. 8 | # 2. Test breakage when run on Windows. 9 | # 10 | # Ref: https://github.com/gradle/native-platform/issues/274 11 | # https://github.com/SpineEventEngine/ProtoData/pull/68 12 | # https://github.com/SpineEventEngine/ProtoData/pull/98 13 | 14 | -------------------------------------------------------------------------------- /gradle-module-plugin/gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | kotlin = "1.7.10" 3 | moshi = "1.9.3" 4 | 5 | [libraries] 6 | # Dependencies referencable in buildscripts. Note that dashes are replaced by periods in the buildscript reference. 7 | guava = { module = "com.google.guava:guava", version = "30.1.1-jre" } 8 | kotlinTest = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } 9 | kotlinTestJunit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } 10 | kotlinXmlBuilder = { module = "org.redundent:kotlin-xml-builder", version = "1.7.2" } 11 | moshi = { module = "com.squareup.moshi:moshi-kotlin", version.ref = "moshi" } 12 | moshiCodegen = { module = "com.squareup.moshi:moshi-kotlin-codegen", version.ref = "moshi" } 13 | moduleSigner = { module = "com.inductiveautomation.ignitionsdk:module-signer", version = "0.0.1.ia" } 14 | 15 | [bundles] 16 | kotlinTest = ["kotlinTest", "kotlinTestJunit"] 17 | -------------------------------------------------------------------------------- /gradle-module-plugin/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inductiveautomation/ignition-module-tools/4fd236ab99605f165c610000eae27b3d13e60f92/gradle-module-plugin/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle-module-plugin/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradle-module-plugin/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /gradle-module-plugin/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 2 | 3 | pluginManagement { 4 | repositories { 5 | gradlePluginPortal() 6 | mavenCentral() 7 | } 8 | } 9 | rootProject.name = "gradle-module-plugin" 10 | 11 | include(":") 12 | 13 | includeBuild("../generator") 14 | -------------------------------------------------------------------------------- /gradle-module-plugin/src/functionalTest/kotlin/io/ia/sdk/gradle/modl/task/AssembleModuleStructureTest.kt: -------------------------------------------------------------------------------- 1 | package io.ia.sdk.gradle.modl.task 2 | 3 | import io.ia.ignition.module.generator.ModuleGenerator 4 | import io.ia.ignition.module.generator.api.GeneratorConfigBuilder 5 | import io.ia.sdk.gradle.modl.BaseTest 6 | import org.junit.Test 7 | import java.nio.file.Files 8 | import kotlin.test.assertTrue 9 | 10 | open class AssembleModuleStructureTest : BaseTest() { 11 | 12 | @Test 13 | fun `single file doc is collected`() { 14 | val projectDir = tempFolder.newFolder(currentMethodName()).toPath() 15 | 16 | val name = "Single Doc" 17 | 18 | val config = GeneratorConfigBuilder() 19 | .moduleName(name) 20 | .scopes("C") 21 | .packageName("check.my.signage") 22 | .parentDir(projectDir) 23 | .useRootForSingleScopeProject(false) 24 | .build() 25 | 26 | val project = ModuleGenerator.generate(config) 27 | 28 | // add some example docs 29 | val docs = project.resolve("docs") 30 | Files.createDirectories(docs) 31 | 32 | val resourcePath = "docs/singleFile/index.html" 33 | // copy our sample docs in 34 | ClassLoader.getSystemResourceAsStream(resourcePath).let { inputStream -> 35 | if (inputStream == null) { 36 | throw Exception("Failed to read test resource 'certs/$resourcePath") 37 | } 38 | val writeTo = docs.resolve("index.html") 39 | inputStream.copyTo(writeTo.toFile().outputStream(), 1024) 40 | 41 | writeTo 42 | } 43 | 44 | // amend the plugin configuration to enable the docs 45 | val rootBuildScript = project.resolve("build.gradle").toFile() 46 | 47 | val buildScriptContents = rootBuildScript.readText().replace( 48 | "// documentationFiles.from(project.file(\"src/docs/\"))", 49 | "documentationFiles.from(project.file(\"docs/\"))" 50 | ).replace( 51 | "// documentationIndex.set(\"index.html\")", 52 | "documentationIndex.set(\"index.html\")" 53 | ) 54 | rootBuildScript.writeText(buildScriptContents) 55 | 56 | runTask(project.toFile(), "assembleModlStructure") 57 | assertTrue( 58 | project.resolve("build/moduleContent/doc/index.html").toFile().exists(), 59 | "doc should exist in staging dir" 60 | ) 61 | } 62 | 63 | @Test 64 | fun `multi file docs are collected`() { 65 | val projectDir = tempFolder.newFolder(currentMethodName()).toPath() 66 | val name = "Multi Doc" 67 | 68 | val config = GeneratorConfigBuilder() 69 | .moduleName(name) 70 | .scopes("C") 71 | .packageName("check.my.signage") 72 | .parentDir(projectDir) 73 | .useRootForSingleScopeProject(false) 74 | .build() 75 | 76 | val project = ModuleGenerator.generate(config) 77 | 78 | // add some example docs 79 | val docs = project.resolve("docs") 80 | Files.createDirectories(docs) 81 | 82 | val resourceNames = setOf("index.html", "linked.html") 83 | // copy our sample docs in 84 | resourceNames.forEach { fileName -> 85 | ClassLoader.getSystemResourceAsStream("docs/multipleFiles/$fileName").let { inputStream -> 86 | if (inputStream == null) { 87 | throw Exception("Failed to read test resource 'certs/$") 88 | } 89 | val writeTo = docs.resolve(fileName) 90 | inputStream.copyTo(writeTo.toFile().outputStream()) 91 | } 92 | } 93 | 94 | // amend the plugin configuration to enable the docs 95 | val rootBuildScript = project.resolve("build.gradle").toFile() 96 | 97 | val buildScriptContents = rootBuildScript.readText().replace( 98 | "// documentationFiles.from(project.file(\"src/docs/\"))", 99 | "documentationFiles.from(project.file(\"docs/\"))" 100 | ).replace( 101 | "// documentationIndex.set(\"index.html\")", 102 | "documentationIndex.set(\"index.html\")" 103 | ) 104 | rootBuildScript.writeText(buildScriptContents) 105 | 106 | runTask(project.toFile(), "assembleModlStructure") 107 | assertTrue( 108 | project.resolve("build/moduleContent/doc/index.html").toFile().exists(), 109 | "index doc file should exist in staging dir" 110 | ) 111 | assertTrue( 112 | project.resolve("build/moduleContent/doc/linked.html").toFile().exists(), 113 | "linked doc file should exist in staging dir" 114 | ) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /gradle-module-plugin/src/functionalTest/kotlin/io/ia/sdk/gradle/modl/task/ModuleBuildReportTest.kt: -------------------------------------------------------------------------------- 1 | package io.ia.sdk.gradle.modl.task 2 | 3 | import io.ia.ignition.module.generator.ModuleGenerator 4 | import io.ia.ignition.module.generator.api.GeneratorConfigBuilder 5 | import io.ia.sdk.gradle.modl.BaseTest 6 | import io.ia.sdk.gradle.modl.model.AssemblyManifest 7 | import io.ia.sdk.gradle.modl.model.MOSHI 8 | import org.junit.Test 9 | import java.io.File 10 | import kotlin.io.path.readText 11 | import kotlin.test.assertEquals 12 | import kotlin.test.assertNotNull 13 | import kotlin.test.assertTrue 14 | 15 | fun jsonToAssemblyManifest(json: String): AssemblyManifest { 16 | val adapter = MOSHI.adapter(AssemblyManifest::class.java) 17 | return adapter.fromJson(json)!! 18 | } 19 | 20 | class ModuleBuildReportTest : BaseTest() { 21 | 22 | @Test 23 | fun `build report is generated`() { 24 | val parentDir: File = tempFolder.newFolder("build_report_generated") 25 | val moduleName = "I Have Reports" 26 | val signingResourcesDestination = parentDir.toPath().resolve("i-have-reports") 27 | 28 | prepareSigningTestResources(signingResourcesDestination) 29 | 30 | val config = GeneratorConfigBuilder() 31 | .moduleName(moduleName) 32 | .scopes("GCD") 33 | .packageName("check.my.signage") 34 | .parentDir(parentDir.toPath()) 35 | .debugPluginConfig(true) 36 | .rootPluginConfig( 37 | """ 38 | id("io.ia.sdk.modl") 39 | """.trimIndent() 40 | ) 41 | .build() 42 | 43 | val projectDir = ModuleGenerator.generate(config) 44 | 45 | runTask(projectDir.toFile(), "modlReport") 46 | val buildDir = projectDir.resolve("build") 47 | val buildJson = buildDir.resolve("buildResult.json") 48 | 49 | assertTrue(buildJson.toFile().exists(), "buildResult.json exists in the build dir") 50 | } 51 | 52 | @Test 53 | fun `build report contains checksum entry`() { 54 | val parentDir: File = tempFolder.newFolder("build_report_checksum") 55 | val moduleName = "I Have Checksum" 56 | val signingResourcesDestination = parentDir.toPath().resolve("i-have-checksum") 57 | 58 | val config = GeneratorConfigBuilder() 59 | .moduleName(moduleName) 60 | .scopes("GCD") 61 | .packageName("check.my.signage") 62 | .parentDir(parentDir.toPath()) 63 | .debugPluginConfig(true) 64 | .rootPluginConfig( 65 | """ 66 | id("io.ia.sdk.modl") 67 | """.trimIndent() 68 | ) 69 | .build() 70 | 71 | val projectDir = ModuleGenerator.generate(config) 72 | prepareSigningTestResources(signingResourcesDestination) 73 | 74 | runTask(projectDir.toFile(), "modlReport") 75 | val buildDir = projectDir.resolve("build") 76 | val buildJson = buildDir.resolve("buildResult.json") 77 | 78 | assertTrue(buildJson.toFile().exists(), "buildResult.json exists in the build dir") 79 | val report = jsonToAssemblyManifest(buildJson.toFile().readText()) 80 | assertNotNull(report.checksum, "checksum entry was present") 81 | } 82 | 83 | @Test 84 | fun `build report contains metainfo entries`() { 85 | val parentDir: File = tempFolder.newFolder("build_report_checksum") 86 | val moduleName = "I Have Checksum" 87 | val signingResourcesDestination = parentDir.toPath().resolve("i-have-checksum") 88 | 89 | val metainfoEntry = """metaInfo.put("test.key", "some string value")""" 90 | val replacement = mapOf("ignitionModule {" to "ignitionModule {\n ${metainfoEntry}\n") 91 | val config = GeneratorConfigBuilder() 92 | .moduleName(moduleName) 93 | .scopes("GCD") 94 | .packageName("check.my.signage") 95 | .parentDir(parentDir.toPath()) 96 | .debugPluginConfig(true) 97 | .rootPluginConfig( 98 | """ 99 | id("io.ia.sdk.modl") 100 | """.trimIndent() 101 | ) 102 | .customReplacements(replacement) 103 | .build() 104 | 105 | val projectDir = ModuleGenerator.generate(config) 106 | prepareSigningTestResources(signingResourcesDestination) 107 | 108 | runTask(projectDir.toFile(), "modlReport") 109 | val buildDir = projectDir.resolve("build") 110 | val buildJson = buildDir.resolve("buildResult.json") 111 | 112 | assertTrue(buildJson.toFile().exists(), "buildResult.json exists in the build dir") 113 | val report = jsonToAssemblyManifest(buildJson.toFile().readText()) 114 | assertEquals(1, report.metaInfo.size, "metainfo should be added to report if configured in plugin") 115 | assertEquals("some string value", report.metaInfo["test.key"], "build report should hold metainfo value") 116 | } 117 | 118 | @Test 119 | fun `build report contains unsigned modl entry when skipModlSigning`() { 120 | val parentDir: File = tempFolder.newFolder("build_report_unsigned") 121 | val moduleName = "Foo" 122 | 123 | val config = GeneratorConfigBuilder() 124 | .moduleName(moduleName) 125 | .scopes("GCD") 126 | .packageName("check.my.signage") 127 | .parentDir(parentDir.toPath()) 128 | .debugPluginConfig(true) 129 | .allowUnsignedModules(true) 130 | .rootPluginConfig( 131 | """ 132 | id("io.ia.sdk.modl") 133 | """.trimIndent() 134 | ) 135 | .build() 136 | 137 | val projectDir = ModuleGenerator.generate(config) 138 | runTask(projectDir.toFile(), "modlReport") 139 | val buildDir = projectDir.resolve("build") 140 | val buildJson = buildDir.resolve("buildResult.json") 141 | 142 | assertTrue(buildJson.toFile().exists(), "buildResult.json should exist in the build dir") 143 | val report = jsonToAssemblyManifest(buildJson.toFile().readText()) 144 | assertNotNull(report.fileName, "filename entry was not present") 145 | assertEquals("Foo.unsigned.modl", report.fileName, "filename entry was not present") 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /gradle-module-plugin/src/functionalTest/kotlin/io/ia/sdk/gradle/modl/task/ZipModuleTests.kt: -------------------------------------------------------------------------------- 1 | package io.ia.sdk.gradle.modl.task 2 | 3 | import io.ia.ignition.module.generator.ModuleGenerator 4 | import io.ia.sdk.gradle.modl.BaseTest 5 | import io.ia.sdk.gradle.modl.util.unsignedModuleName 6 | import org.junit.Test 7 | import kotlin.test.assertTrue 8 | 9 | class ZipModuleTests : BaseTest() { 10 | 11 | @Test 12 | fun `unsigned module is built and has appropriate name`() { 13 | val name = "Some Thing" 14 | val config = config(name, "GC", "net.some.thing") 15 | val projectDir = ModuleGenerator.generate(config) 16 | runTask(projectDir.toFile(), "zipModule") 17 | 18 | val buildDir = projectDir.resolve("build") 19 | val zipFileName = unsignedModuleName(name) 20 | 21 | assertTrue(buildDir.toFile().exists()) 22 | assertTrue(buildDir.resolve(zipFileName).toFile().exists(), "Unsigned module created with correct name") 23 | } 24 | 25 | fun `unzipping unsigned module reveals correct contents`() { 26 | val name = "Unzip Me" 27 | 28 | val config = config(name, "G", "unpack.the.pack") 29 | val projectDir = ModuleGenerator.generate(config) 30 | runTask(projectDir.toFile(), "zipModule") 31 | 32 | val buildDir = projectDir.resolve("build") 33 | val zipFileName = unsignedModuleName(name) 34 | assertTrue(buildDir.toFile().exists()) 35 | assertTrue(buildDir.resolve(zipFileName).toFile().exists()) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /gradle-module-plugin/src/functionalTest/kotlin/io/ia/sdk/gradle/modl/util/utils.kt: -------------------------------------------------------------------------------- 1 | package io.ia.sdk.gradle.modl.util 2 | 3 | fun unsignedModuleName(humanModuleName: String): String { 4 | return "${humanModuleName.replace(" ", "-")}.unsigned.modl" 5 | } 6 | 7 | fun signedModuleName(humanModuleName: String): String { 8 | return unsignedModuleName(humanModuleName).replace(".unsigned", "") 9 | } 10 | 11 | fun nameToDirName(moduleName: String): String { 12 | return moduleName.split(" ").joinToString("-") { it.lowercase() } 13 | } 14 | 15 | // For when you don't need full-blown XML parsing just to test. Smoosh all 16 | // tags together in one long line by knocking out indentation and newlines. 17 | fun collapseXmlToOneLine(xml: String): String = 18 | xml.replace(Regex("""^\s+"""), "").replace(Regex("""\R"""), "") 19 | 20 | // Likewise, for when it's useful to break XML nodes out to a list of node 21 | // names. With optional inclusive filter. 22 | fun splitXmlNodesToList( 23 | xml: String, 24 | nodeFilter: List = listOf(), 25 | ): List = 26 | xml.split(Regex("""(?<=>)\s+(?=<)""")) 27 | .filter { n -> 28 | // if no filter, include; else look for BOL with matching tag 29 | nodeFilter.isEmpty() || 30 | nodeFilter.any { nf -> n.startsWith("<$nf") } 31 | } 32 | -------------------------------------------------------------------------------- /gradle-module-plugin/src/functionalTest/resources/certs/README.md: -------------------------------------------------------------------------------- 1 | # Warning! 2 | 3 | The certificates and settings in this directory are not secure, and should not be used outside the bounds of functional testing. 4 | 5 | -------------------------------------------------------------------------------- /gradle-module-plugin/src/functionalTest/resources/certs/certificate.cer: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDhTCCAm2gAwIBAgIESrnORTANBgkqhkiG9w0BAQsFADBzMQswCQYDVQQGEwJV 3 | UzETMBEGA1UECBMKQ2FsaWZvcm5pYTETMBEGA1UEBxMKU2FjcmFtZW50bzEOMAwG 4 | A1UEChMFaWEuaW8xFDASBgNVBAsTC2RldmVsb3BtZW50MRQwEgYDVQQDEwtwZXJy 5 | eSBqb25lczAeFw0yMDA2MDUyMTM4NTVaFw00NzEwMjEyMTM4NTVaMHMxCzAJBgNV 6 | BAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRMwEQYDVQQHEwpTYWNyYW1lbnRv 7 | MQ4wDAYDVQQKEwVpYS5pbzEUMBIGA1UECxMLZGV2ZWxvcG1lbnQxFDASBgNVBAMT 8 | C3BlcnJ5IGpvbmVzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgoAr 9 | Qd/P2Jdloe6EOXPGZ6k+FhG9G1YPfW2tqFguanl6RCarxR8c1KTUVjScNRA5RwsW 10 | JGyXCZp67pvYZIZWqgSN8gDen3Pj351Xz6BjTz7Wu1V4m/Fq0m1O6xbH5xiYvUxj 11 | r9Zb8RR7x8xb6nBeemmYuM144rc7XkS3pMZS/7NI1v4OMa7AkyQr+lBM11jRtZ+s 12 | N34Bqrbjw9pdCA+YOJHJmRdCoUPlBiTkHlJ5qJldaENPpupcv2MLeG2d+fmdoH32 13 | VE3EwerZGUcIIt8mvRVR6a4lABahW3xUZzRaYsYCjIEDa8k9Zww7LSGJfh21da4H 14 | wAut4gf18RKq4zjaYwIDAQABoyEwHzAdBgNVHQ4EFgQU2a3jPD5j1gd/JRFn+w55 15 | +ENKqogwDQYJKoZIhvcNAQELBQADggEBADKeNSXuN1zU+t/qEkW1HOJJyg2zDCVK 16 | DnJIC0p6N1lJ860vxIfFYMnCN1QN6Q2fwBNmX74nQHaF2CiX6P4JS9YfrKtmLCBi 17 | lND6aZZct3OuL3MnbM+ILr+EQNAHSS1ucLL6QMhuZvhscoloY8B5OGghpM35Vydh 18 | 0RRn8W94CKCsje9NgG3aSJpXWc7thS1aYkuMIryukxfAIxAhtnJWc/1HbMiLV6+R 19 | dUigzqgQklkQ1hRmxAOqq8o+6zoEGLU8s/LuILxLuqKsZH31khqEKG3C3FvphGzW 20 | gKfDQx2RmuNnutObX9OzKKlXsbNAsW+gp3Yvy2XFtTI4JIYpQ9OALpw= 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /gradle-module-plugin/src/functionalTest/resources/certs/certificate.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDhTCCAm2gAwIBAgIESrnORTANBgkqhkiG9w0BAQsFADBzMQswCQYDVQQGEwJV 3 | UzETMBEGA1UECBMKQ2FsaWZvcm5pYTETMBEGA1UEBxMKU2FjcmFtZW50bzEOMAwG 4 | A1UEChMFaWEuaW8xFDASBgNVBAsTC2RldmVsb3BtZW50MRQwEgYDVQQDEwtwZXJy 5 | eSBqb25lczAeFw0yMDA2MDUyMTM4NTVaFw00NzEwMjEyMTM4NTVaMHMxCzAJBgNV 6 | BAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRMwEQYDVQQHEwpTYWNyYW1lbnRv 7 | MQ4wDAYDVQQKEwVpYS5pbzEUMBIGA1UECxMLZGV2ZWxvcG1lbnQxFDASBgNVBAMT 8 | C3BlcnJ5IGpvbmVzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgoAr 9 | Qd/P2Jdloe6EOXPGZ6k+FhG9G1YPfW2tqFguanl6RCarxR8c1KTUVjScNRA5RwsW 10 | JGyXCZp67pvYZIZWqgSN8gDen3Pj351Xz6BjTz7Wu1V4m/Fq0m1O6xbH5xiYvUxj 11 | r9Zb8RR7x8xb6nBeemmYuM144rc7XkS3pMZS/7NI1v4OMa7AkyQr+lBM11jRtZ+s 12 | N34Bqrbjw9pdCA+YOJHJmRdCoUPlBiTkHlJ5qJldaENPpupcv2MLeG2d+fmdoH32 13 | VE3EwerZGUcIIt8mvRVR6a4lABahW3xUZzRaYsYCjIEDa8k9Zww7LSGJfh21da4H 14 | wAut4gf18RKq4zjaYwIDAQABoyEwHzAdBgNVHQ4EFgQU2a3jPD5j1gd/JRFn+w55 15 | +ENKqogwDQYJKoZIhvcNAQELBQADggEBADKeNSXuN1zU+t/qEkW1HOJJyg2zDCVK 16 | DnJIC0p6N1lJ860vxIfFYMnCN1QN6Q2fwBNmX74nQHaF2CiX6P4JS9YfrKtmLCBi 17 | lND6aZZct3OuL3MnbM+ILr+EQNAHSS1ucLL6QMhuZvhscoloY8B5OGghpM35Vydh 18 | 0RRn8W94CKCsje9NgG3aSJpXWc7thS1aYkuMIryukxfAIxAhtnJWc/1HbMiLV6+R 19 | dUigzqgQklkQ1hRmxAOqq8o+6zoEGLU8s/LuILxLuqKsZH31khqEKG3C3FvphGzW 20 | gKfDQx2RmuNnutObX9OzKKlXsbNAsW+gp3Yvy2XFtTI4JIYpQ9OALpw= 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /gradle-module-plugin/src/functionalTest/resources/certs/keystore.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inductiveautomation/ignition-module-tools/4fd236ab99605f165c610000eae27b3d13e60f92/gradle-module-plugin/src/functionalTest/resources/certs/keystore.jks -------------------------------------------------------------------------------- /gradle-module-plugin/src/functionalTest/resources/certs/pkcs11-yk5-win.cfg: -------------------------------------------------------------------------------- 1 | # Windows-friendly config for the YubiKey 5. The library path is typical for 2 | # an install of the Yubico PIV Tool, which includes that PKCS#11 DLL and 3 | # supporting DLLs. Adjust that path if you install the DLLs by different 4 | # means and place them in a different directory. 5 | name=YubiKey5 6 | library=C:\\Program Files\\Yubico\\Yubico PIV Tool\\bin\\libykcs11.dll 7 | -------------------------------------------------------------------------------- /gradle-module-plugin/src/functionalTest/resources/certs/pkcs11-yk5-win.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICqjCCAZKgAwIBAgIUX1q+xNWuBJvIOLFtAWf/TwVVoxwwDQYJKoZIhvcNAQEL 3 | BQAwDzENMAsGA1UEAwwEYnJheTAeFw0yNDAyMTQyMjI3MDdaFw0yNTAyMTQwMDAw 4 | MDBaMA8xDTALBgNVBAMMBGJyYXkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK 5 | AoIBAQDcRYrMiF1ZrtZf2MEUUREDWDVCJdjT8iDgTL9+vbhQ6rrKr6hKLYnx8mrr 6 | /ErqsHcZBpNNJiHIlYpk+CiYp/orulnFDOd4gXsgyCZuRfVdnm7SrZ26/OSW4let 7 | coWqcVitC5oQp162b7w4Rg/Dh4NyDcp31AIi+IksMJy9h6YZM9uSfnFx9QLbbrWt 8 | N3/ZnzvxX2TaEGaSVydH1ewBZ1DB9em3wQjpHCXySb0nqb2LcJM4/Hm02iOSccJb 9 | dEqD5b3y9rklHkGgZQep1ZEmgc/2fStyyMh9Hp11yyiKN+jS6HECOFy6ni9W61oZ 10 | ERyD/YYuGUi2vsBsaez1HIk87fWrAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAAM5 11 | 4ypSAFXbCNTfJWnLoL/VAQ0NK69cw/RR1RDwbcXHtMgfMZCmcmgeLSbo8IXs3u77 12 | t3Wrs1DCFYKWx27/e3Weg4Oz9TOq8p/e8lHiKW9ul5Gy+5JW4hr5D9AN4bqiEMZm 13 | xlA5ANKUampPjgdXw9Ssv+z2MlVyocGWM2B31nT8yf6Yz9ti/beT/oZBZq0tO9ok 14 | iv5OSeBW2sMnD8Yk28yduLkaxBNyMA605oyvK0C87inYuUGrGLKUnDU9vt9+sQCE 15 | cGNUlA4ag/tfEpZ6A6Qm1zHsFKrpPI98PwiOzwIjE84MXifWwHPZRgQb5JAwh8xD 16 | hH/iJduNQo573Sh6mGI= 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /gradle-module-plugin/src/functionalTest/resources/certs/pkcs11.cfg: -------------------------------------------------------------------------------- 1 | name = OpenSC 2 | library = /usr/lib/x86_64-linux-gnu/opensc-pkcs11.so 3 | description = OpenSC PKCS11 Provider 4 | -------------------------------------------------------------------------------- /gradle-module-plugin/src/functionalTest/resources/common/common.build.gradle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inductiveautomation/ignition-module-tools/4fd236ab99605f165c610000eae27b3d13e60f92/gradle-module-plugin/src/functionalTest/resources/common/common.build.gradle -------------------------------------------------------------------------------- /gradle-module-plugin/src/functionalTest/resources/designer/DesignerHook.java: -------------------------------------------------------------------------------- 1 | package le.examp; 2 | 3 | import com.inductiveautomation.ignition.common.licensing.LicenseState; 4 | import com.inductiveautomation.ignition.designer.model.AbstractDesignerModuleHook; 5 | import com.inductiveautomation.ignition.designer.model.DesignerContext; 6 | 7 | 8 | /** 9 | * This is the Designer-scope module hook used for tests. 10 | */ 11 | public class DesignerHook extends AbstractDesignerModuleHook { 12 | 13 | public static final String MODULE_ID = "component-example"; 14 | 15 | @Override 16 | public void startup(DesignerContext context, LicenseState activationState) throws Exception { 17 | // noop 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /gradle-module-plugin/src/functionalTest/resources/designer/designer.build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "java-library" 3 | } 4 | 5 | sourceCompatibility = JavaVersion.VERSION_11 6 | targetCompatibility = JavaVersion.VERSION_11 7 | 8 | dependencies { 9 | compileOnly("com.inductiveautomation.ignitionsdk:designer-api:${sdk_version}") 10 | compileOnly("com.inductiveautomation.ignitionsdk:ignition-common:${sdk_version}") 11 | } 12 | -------------------------------------------------------------------------------- /gradle-module-plugin/src/functionalTest/resources/docs/multipleFiles/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Example Documentation 5 | 6 | 7 |

Here Are Your Module Docs

8 |

This is an example doc paragraph with a link

9 | 10 | 11 | -------------------------------------------------------------------------------- /gradle-module-plugin/src/functionalTest/resources/docs/multipleFiles/linked.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Linked Documentation 5 | 6 | 7 |

Some Linked Module Docs

8 |

This is an example doc paragraph. To go to the index page, click the link

9 | 10 | 11 | -------------------------------------------------------------------------------- /gradle-module-plugin/src/functionalTest/resources/docs/singleFile/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Example Documentation 5 | 6 | 7 |

This is an example doc paragraph.

8 | 9 | 10 | -------------------------------------------------------------------------------- /gradle-module-plugin/src/functionalTest/resources/gateway/GatewayHook.java: -------------------------------------------------------------------------------- 1 | package ; 2 | 3 | import java.util.List; 4 | 5 | import com.inductiveautomation.ignition.common.expressions.ExpressionFunctionManager; 6 | import com.inductiveautomation.ignition.common.licensing.LicenseState; 7 | import com.inductiveautomation.ignition.common.script.ScriptManager; 8 | import com.inductiveautomation.ignition.common.xmlserialization.deserialization.XMLDeserializer; 9 | import com.inductiveautomation.ignition.common.xmlserialization.serialization.XMLSerializer; 10 | import com.inductiveautomation.ignition.gateway.AbstractGatewayModuleHook; 11 | import com.inductiveautomation.ignition.gateway.clientcomm.ClientReqSession; 12 | import com.inductiveautomation.ignition.gateway.web.models.INamedTab; 13 | 14 | class GatewayHook extends AbstractGatewayModuleHook { 15 | @Override 16 | public Object getRPCHandler(ClientReqSession session, String projectName) { 17 | return null; 18 | } 19 | 20 | @Override 21 | public List getHomepagePanels() { 22 | return null; 23 | } 24 | 25 | @Override 26 | public List getStatusPanels() { 27 | return null; 28 | } 29 | 30 | @Override 31 | public void configureDeserializer(XMLDeserializer deserializer) { 32 | 33 | } 34 | 35 | @Override 36 | public void configureSerializer(XMLSerializer serializer) { 37 | 38 | } 39 | 40 | @Override 41 | public void configureFunctionFactory(ExpressionFunctionManager factory) { 42 | 43 | } 44 | 45 | @Override 46 | public void notifyLicenseStateChanged(LicenseState licenseState) { 47 | 48 | } 49 | 50 | @Override 51 | public void initializeScriptManager(ScriptManager manager) { 52 | 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /gradle-module-plugin/src/functionalTest/resources/gateway/gateway.build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "java-library" 3 | } 4 | 5 | sourceCompatibility = JavaVersion.VERSION_11 6 | targetCompatibility = JavaVersion.VERSION_11 7 | 8 | dependencies { 9 | compileOnly("com.inductiveautomation.ignitionsdk:ignition-common:${sdk_version}") 10 | compileOnly ("com.inductiveautomation.ignitionsdk:gateway-api:${sdk_version}") 11 | } 12 | 13 | -------------------------------------------------------------------------------- /gradle-module-plugin/src/functionalTest/resources/java/NotRealClass1.java: -------------------------------------------------------------------------------- 1 | package hot.sources; 2 | 3 | public class NotRealClass1 { 4 | static final String WHAT_AM_I = "I'm just a class for automated test purposes. The first."; 5 | } 6 | -------------------------------------------------------------------------------- /gradle-module-plugin/src/main/kotlin/io/ia/sdk/gradle/modl/api/Constants.kt: -------------------------------------------------------------------------------- 1 | package io.ia.sdk.gradle.modl.api 2 | 3 | object Constants { 4 | /** 5 | * Name for directory created in a plugin project's `build` folder 6 | */ 7 | @JvmStatic 8 | val LOCAL_PROJECT_WORKDIR: String = "module" 9 | 10 | @JvmStatic 11 | val ARTIFACT_DIR: String = "artifacts" 12 | 13 | /** 14 | * "_Working directory_" used by the root module project (where the plugin is applied). The files and information 15 | * from a module's build is aggreggated into this directory, the contents of which are ultimately zipped into the 16 | * .modl file. 17 | */ 18 | @JvmStatic 19 | val MODULE_BUILD_WORKDIR = "$LOCAL_PROJECT_WORKDIR/modl" 20 | 21 | @JvmStatic 22 | val MODULE_API_CONFIGURATION = "modlApi" 23 | 24 | @JvmStatic 25 | val MODULE_IMPLEMENTATION_CONFIGURATION = "modlImplementation" 26 | 27 | /** 28 | * A transitive, resolvable configuration that extends the `modlImplementation` configuration for internal 29 | * resolution and collection of `modlImplementation` dependencies, and their transitive dependencies. This extra 30 | * configuration layer allows for correct IDE/tooling visibibility of `implementation` dependencies, while also 31 | * allowing for the collection of those dependencies for inclusion in to a module. 32 | */ 33 | @JvmStatic 34 | val MODULE_IMPLEMENTATION_ELEMENTS = "modlImplementationElements" 35 | 36 | /** 37 | * Flag checked when setting up the plugin's artifact repository sources. By default, the plugin adds the Inductive 38 | * Automation Maven artifact repositories to the project's configuration, allowing the ability to resolve Ignition 39 | * Java dependencies. If this is undesirable, executing the build with this flag's value set to 'false' will skip 40 | * the auto-application of the IA repo. 41 | * 42 | * _Excluding the IA repos is generally undesirable, as dependencies are required to compile a module. This flag 43 | * is provided for troubleshooting/debugging, those using a private artifact repo system that is mirroring or 44 | * proxying the IA repository, or in the event the Inductive Automation repo URL changes and the incorrect URL is 45 | * failing the build._ 46 | * 47 | * Example Usage: `./gradlew assemble -PapplyIaMavenRepository=false` 48 | * Default value: no default value applied. Empty value treated as if `applyIaMavenRepository=true` were applied. 49 | */ 50 | @JvmStatic 51 | val APPLY_IA_REPOSITORY_FLAG = "applyIaMavenRepository" 52 | 53 | /** 54 | * Signing properties located in gradle.properties files will be namespaced under this prefix in order to avoid 55 | * collisions. 56 | */ 57 | @JvmStatic 58 | val PROPERTY_NAMESPACE = "ignition.signing" 59 | 60 | /** 61 | * CLI flag/Property Suffix (see readme) for the certificate alias used when signing the built module. 62 | */ 63 | const val ALIAS_FLAG: String = "certAlias" 64 | 65 | /** 66 | * CLI flag/Property suffix (see readme) for the certifcate (file) that is used for signing the built module. 67 | */ 68 | const val CERT_FILE_FLAG: String = "certFile" 69 | 70 | /** 71 | * Password for the certificate used to sign the built module. 72 | */ 73 | const val CERT_PW_FLAG: String = "certPassword" 74 | 75 | /** 76 | * The keystore file to be used for signing the module. Not compatible 77 | * with [PKCS11_CFG_FILE_FLAG]. 78 | */ 79 | const val KEYSTORE_FILE_FLAG: String = "keystoreFile" 80 | 81 | /** 82 | * Keystore password to use in signing the module 83 | */ 84 | const val KEYSTORE_PW_FLAG: String = "keystorePassword" 85 | 86 | /** 87 | * PKCS#11 config file, typically used for hardware token keystores and 88 | * other HSMs. Not compatible with [KEYSTORE_FILE_FLAG]. 89 | */ 90 | const val PKCS11_CFG_FILE_FLAG: String = "pkcs11CfgFile" 91 | 92 | /** 93 | * Map of CLI option flag `gradle --taskProperty=value` to namespaced gradle property equivalent. 94 | * 95 | * Examples: 96 | * 97 | * | Task Flag | Project Property | Property File (in gradle.properties, or as -P property)| 98 | * |------------------------------------------------------------| 99 | * |gradle signModule --certAlias=someAlias | gradle signModule -Pignition.signning.certAlias=someAlias | ignition.signing.certAlias=someAlias| 100 | */ 101 | val SIGNING_PROPERTIES: Map = mapOf( 102 | ALIAS_FLAG to "$PROPERTY_NAMESPACE.$ALIAS_FLAG", 103 | CERT_FILE_FLAG to "$PROPERTY_NAMESPACE.$CERT_FILE_FLAG", 104 | CERT_PW_FLAG to "$PROPERTY_NAMESPACE.$CERT_PW_FLAG", 105 | KEYSTORE_FILE_FLAG to "$PROPERTY_NAMESPACE.$KEYSTORE_FILE_FLAG", 106 | KEYSTORE_PW_FLAG to "$PROPERTY_NAMESPACE.$KEYSTORE_PW_FLAG", 107 | PKCS11_CFG_FILE_FLAG to "$PROPERTY_NAMESPACE.$PKCS11_CFG_FILE_FLAG", 108 | ) 109 | } 110 | -------------------------------------------------------------------------------- /gradle-module-plugin/src/main/kotlin/io/ia/sdk/gradle/modl/api/ModuleConfigException.kt: -------------------------------------------------------------------------------- 1 | package io.ia.sdk.gradle.modl.api 2 | 3 | open class ModuleConfigException(message: String) : 4 | Exception("There was an error in applying the ignitionModule settings in the build.gradle, $message") 5 | -------------------------------------------------------------------------------- /gradle-module-plugin/src/main/kotlin/io/ia/sdk/gradle/modl/extension/ModuleDependencySpec.kt: -------------------------------------------------------------------------------- 1 | package io.ia.sdk.gradle.modl.extension 2 | 3 | import org.gradle.api.Named 4 | import org.gradle.api.tasks.Input 5 | import java.io.Serializable 6 | 7 | abstract class ModuleDependencySpec : Named, Serializable { 8 | @get:Input 9 | var scope: String = "" 10 | 11 | @get:Input 12 | var required: Boolean = false 13 | } 14 | -------------------------------------------------------------------------------- /gradle-module-plugin/src/main/kotlin/io/ia/sdk/gradle/modl/model/Models.kt: -------------------------------------------------------------------------------- 1 | package io.ia.sdk.gradle.modl.model 2 | 3 | import io.ia.sdk.gradle.modl.api.ModuleConfigException 4 | import org.gradle.api.Project 5 | import org.gradle.api.model.ObjectFactory 6 | import org.gradle.api.provider.ListProperty 7 | 8 | /** 9 | * Simple container representing a module dependency, where the scope is a string signifying the Ignition scope in which 10 | * the dependency should apply, and the moduleId is that of the module that is being depended on for that scope. 11 | */ 12 | data class ModuleDependency(val moduleId: String, val scopes: List) 13 | 14 | @Suppress("UnstableApiUsage") 15 | open class ProjectScopeContainer(private val scope: IgnitionScope, private val factory: ObjectFactory) { 16 | private var projects: ListProperty = this.factory.listProperty(Project::class.java) 17 | 18 | public fun getScope(): IgnitionScope { 19 | return this.scope 20 | } 21 | } 22 | 23 | enum class IgnitionScope(val code: String) { 24 | DESIGNER("D"), 25 | VISION_CLIENT("C"), 26 | GATEWAY("G"), 27 | DESIGNER_VISION_CLIENT("CD"), 28 | GATEWAY_DESIGNER("DG"), 29 | GATEWAY_DESIGNER_VISION_CLIENT("CDG"), 30 | GATEWAY_VISION_CLIENT("CG"), 31 | ALL("A"), 32 | NONE(""); 33 | 34 | companion object { 35 | private val VALID_SCOPES = Regex("^[AGCD]+\$") 36 | 37 | /** 38 | * Applies simplistic validation to the string and returns the corresponding scope if it can be determined. 39 | */ 40 | @JvmStatic 41 | @Throws(ModuleConfigException::class) 42 | fun forShorthand(code: String?): IgnitionScope { 43 | if (code == null) { 44 | throw Exception("Null is not a valid value for IgnitionScope code.") 45 | } 46 | 47 | val capped = code.toCharArray().sorted().joinToString("") 48 | 49 | if (!capped.matches(VALID_SCOPES)) { 50 | throw ModuleConfigException( 51 | "'$code' is not a valid Ignition scope value, should be a string of " + 52 | "one or more of G, C, or D (without repeats)." 53 | ) 54 | } 55 | 56 | return when (capped) { 57 | "C" -> VISION_CLIENT 58 | "D" -> DESIGNER 59 | "G" -> GATEWAY 60 | "CD" -> DESIGNER_VISION_CLIENT 61 | "CDG" -> GATEWAY_DESIGNER_VISION_CLIENT 62 | "DG" -> GATEWAY_DESIGNER 63 | "CG" -> GATEWAY_VISION_CLIENT 64 | "A" -> ALL 65 | else -> throw Exception("Could not determine IgnitionScope for shorthand '$code'") 66 | } 67 | } 68 | 69 | /** 70 | * Performs 'CDG'-to-'A' transform when applicable. 71 | */ 72 | @JvmStatic 73 | @Throws(ModuleConfigException::class) 74 | fun promoteToAllWhenImplied(scopes: String): IgnitionScope = 75 | forShorthand(scopes).let { scope -> 76 | if (scope == GATEWAY_DESIGNER_VISION_CLIENT) 77 | ALL else scope 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /gradle-module-plugin/src/main/kotlin/io/ia/sdk/gradle/modl/model/manifests.kt: -------------------------------------------------------------------------------- 1 | package io.ia.sdk.gradle.modl.model 2 | 3 | import com.squareup.moshi.JsonAdapter 4 | import com.squareup.moshi.Moshi 5 | import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory 6 | import java.io.File 7 | import java.io.Serializable 8 | 9 | data class FileArtifact(val id: String, val jarFile: File) 10 | 11 | data class Artifact(val id: String, val jarName: String) : Serializable { 12 | constructor(fileArtifact: FileArtifact) : this( 13 | fileArtifact.id, 14 | fileArtifact.jarFile.name 15 | ) 16 | } 17 | 18 | data class ArtifactManifest( 19 | val projectPath: String, 20 | val projectName: String, 21 | val scope: String, 22 | val artifacts: List 23 | ) : Serializable 24 | 25 | val MOSHI: Moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build() 26 | 27 | fun artifactManifestFromJson(json: String): ArtifactManifest { 28 | val adapter = MOSHI.adapter(ArtifactManifest::class.java).indent(" ") 29 | return adapter.fromJson(json) as ArtifactManifest 30 | } 31 | 32 | fun ArtifactManifest.toJson(): String { 33 | val adapter = MOSHI.adapter(ArtifactManifest::class.java).indent(" ") 34 | return adapter.toJson(this) 35 | } 36 | 37 | data class ChecksumResult( 38 | val checksum: String, 39 | val algorithm: String 40 | ) 41 | 42 | val checksumAdapter: JsonAdapter = MOSHI.adapter(ChecksumResult::class.java) 43 | 44 | /** 45 | * Creates instance of [ChecksumResult] from json string. 46 | */ 47 | fun jsonToChecksumResult(json: String): ChecksumResult { 48 | return checksumAdapter.fromJson(json) as ChecksumResult 49 | } 50 | 51 | /** 52 | * Creates JSON string of the checksum result. 53 | */ 54 | fun ChecksumResult.toJson(): String { 55 | return checksumAdapter.toJson(this) 56 | } 57 | 58 | data class AssemblyManifest( 59 | val moduleId: String, 60 | val moduleName: String, 61 | val moduleDescription: String, 62 | val version: String, 63 | val checksum: ChecksumResult, 64 | val fileName: String, 65 | val fileSize: Long, 66 | val artifacts: Map, // project path to manifest 67 | val metaInfo: Map 68 | ) 69 | 70 | val adapter = MOSHI.adapter(AssemblyManifest::class.java).indent(" ") 71 | 72 | fun AssemblyManifest.toJson(): String { 73 | return adapter.toJson(this) 74 | } 75 | 76 | fun assemblyManifestFromJson(json: String): AssemblyManifest { 77 | return adapter.fromJson(json) as AssemblyManifest 78 | } 79 | -------------------------------------------------------------------------------- /gradle-module-plugin/src/main/kotlin/io/ia/sdk/gradle/modl/task/AssembleModuleStructure.kt: -------------------------------------------------------------------------------- 1 | package io.ia.sdk.gradle.modl.task 2 | 3 | import io.ia.sdk.gradle.modl.PLUGIN_TASK_GROUP 4 | import org.gradle.api.DefaultTask 5 | import org.gradle.api.file.ConfigurableFileCollection 6 | import org.gradle.api.file.DirectoryProperty 7 | import org.gradle.api.file.DuplicatesStrategy 8 | import org.gradle.api.model.ObjectFactory 9 | import org.gradle.api.provider.Property 10 | import org.gradle.api.provider.SetProperty 11 | import org.gradle.api.tasks.Input 12 | import org.gradle.api.tasks.InputFiles 13 | import org.gradle.api.tasks.Optional 14 | import org.gradle.api.tasks.OutputDirectory 15 | import org.gradle.api.tasks.PathSensitive 16 | import org.gradle.api.tasks.PathSensitivity 17 | import org.gradle.api.tasks.TaskAction 18 | import java.io.File 19 | 20 | /** 21 | * Task which applies to the root gradle project (aka, the project applying the plugin) and collects the assets 22 | * created by one or more [CollectModlDependencies] tasks. 23 | */ 24 | open class AssembleModuleStructure @javax.inject.Inject constructor(objects: ObjectFactory) : DefaultTask() { 25 | 26 | companion object { 27 | const val ID = "assembleModlStructure" 28 | } 29 | 30 | init { 31 | this.group = PLUGIN_TASK_GROUP 32 | this.description = 33 | "Assembles module assets into the 'moduleContent' folder in the module project's build directory." 34 | } 35 | 36 | /** 37 | * Folder that assets will be collected into. 38 | */ 39 | @get:OutputDirectory 40 | val moduleContentDir: DirectoryProperty = objects.directoryProperty() 41 | 42 | @get:Input 43 | val duplicateStrategy: Property = objects.property(DuplicatesStrategy::class.java).convention( 44 | DuplicatesStrategy.WARN 45 | ) 46 | 47 | /** 48 | * The collection of files that are to be placed into a `docs` directory in the module 49 | */ 50 | @get:Optional 51 | @get:InputFiles 52 | val docFiles: ConfigurableFileCollection = objects.fileCollection() 53 | 54 | @get:Optional 55 | @get:Input 56 | val docIndexPath: Property = objects.property(String::class.java) 57 | 58 | /** 59 | * Source directories for assets provided by subprojects 60 | */ 61 | @get:InputFiles 62 | @get:PathSensitive(PathSensitivity.RELATIVE) 63 | val moduleArtifactDirs: SetProperty = objects.setProperty(DirectoryProperty::class.java) 64 | 65 | @get:Input 66 | val license: Property = objects.property(String::class.java) 67 | 68 | @TaskAction 69 | fun execute() { 70 | project.logger.info("Assembling module structure in '${moduleContentDir.get().asFile.absolutePath}'") 71 | 72 | val sources = moduleArtifactDirs.get().map { it.get() } 73 | 74 | project.copy { copySpec -> 75 | copySpec.from(sources) 76 | copySpec.into(moduleContentDir) 77 | copySpec.exclude(CollectModlDependencies.JSON_FILENAME) 78 | copySpec.duplicatesStrategy = duplicateStrategy.get() 79 | } 80 | 81 | if (license.isPresent && license.get().isNotEmpty()) { 82 | project.copy { 83 | it.from(license.get()) 84 | it.into(moduleContentDir) 85 | it.duplicatesStrategy = duplicateStrategy.get() 86 | } 87 | } 88 | 89 | if (!docFiles.isEmpty) { 90 | if (docIndexPath.isPresent) { 91 | val indexPath = docIndexPath.get() 92 | 93 | val moduleDocRoot = File(moduleContentDir.asFile.get(), "doc") 94 | project.copy { 95 | it.from(docFiles) 96 | it.into(moduleDocRoot) 97 | it.duplicatesStrategy = duplicateStrategy.get() 98 | } 99 | 100 | if (!File(moduleDocRoot, indexPath).exists()) { 101 | throw Exception("$indexPath not found in $moduleDocRoot, check module documentation configuration.") 102 | } 103 | } else throw Exception( 104 | """ 105 | Documentation files were declared, but documentationIndexPath was not set. Check ignitionModule 106 | configuration. 107 | """.trimIndent() 108 | ) 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /gradle-module-plugin/src/main/kotlin/io/ia/sdk/gradle/modl/task/Checksum.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("UnstableApiUsage") 2 | 3 | package io.ia.sdk.gradle.modl.task 4 | 5 | import com.google.common.hash.HashFunction 6 | import com.google.common.hash.Hashing 7 | import com.google.common.io.Files 8 | import io.ia.sdk.gradle.modl.PLUGIN_TASK_GROUP 9 | import io.ia.sdk.gradle.modl.model.ChecksumResult 10 | import io.ia.sdk.gradle.modl.model.toJson 11 | import org.gradle.api.DefaultTask 12 | import org.gradle.api.file.ProjectLayout 13 | import org.gradle.api.file.RegularFileProperty 14 | import org.gradle.api.model.ObjectFactory 15 | import org.gradle.api.provider.Property 16 | import org.gradle.api.tasks.Input 17 | import org.gradle.api.tasks.InputFile 18 | import org.gradle.api.tasks.OutputFile 19 | import org.gradle.api.tasks.PathSensitive 20 | import org.gradle.api.tasks.PathSensitivity 21 | import org.gradle.api.tasks.TaskAction 22 | import java.io.FileNotFoundException 23 | import java.io.Serializable 24 | import javax.inject.Inject 25 | 26 | open class Checksum @Inject constructor(_objects: ObjectFactory, _layout: ProjectLayout) : DefaultTask() { 27 | companion object { 28 | const val ID = "checksumModl" 29 | } 30 | 31 | @get:InputFile 32 | @get:PathSensitive(PathSensitivity.RELATIVE) 33 | val modlFile: RegularFileProperty = _objects.fileProperty() 34 | 35 | @get:OutputFile 36 | val checksumJson: RegularFileProperty = _objects.fileProperty().convention( 37 | _layout.buildDirectory.file("checksum/checksum.json") 38 | ) 39 | 40 | /** 41 | * Hash function to use against the module file to get the file's checksum. 42 | */ 43 | @get:Input 44 | val hashAlgorithm: Property = _objects.property(HashAlgorithm::class.java) 45 | 46 | @TaskAction 47 | fun execute() { 48 | val module = modlFile.get().asFile 49 | 50 | if (module.exists()) { 51 | val digest = Files.asByteSource(module).hash(hashImpl(hashAlgorithm.get())) 52 | val checksumInfo = ChecksumResult(digest.toString(), hashAlgorithm.get().name) 53 | checksumJson.get().asFile.writeText(checksumInfo.toJson()) 54 | } else { 55 | throw FileNotFoundException("Could not generate checksum for '$module'") 56 | } 57 | } 58 | 59 | init { 60 | this.group = PLUGIN_TASK_GROUP 61 | this.description = """ 62 | |Executes a hash function (default SHA256) against the modl file, and emits a json file to the build 63 | | directory containing the digest. 64 | """.trimMargin() 65 | } 66 | } 67 | 68 | /** 69 | * Simple mapping of our internal enum to the implementation, to avoid configuration-breaking changes down the road 70 | * should we need to break away from Guava or want to add alternatives. 71 | */ 72 | private fun hashImpl(config: HashAlgorithm): HashFunction { 73 | return when (config) { 74 | HashAlgorithm.SHA256 -> Hashing.sha256() 75 | HashAlgorithm.SHA384 -> Hashing.sha384() 76 | HashAlgorithm.SHA512 -> Hashing.sha512() 77 | } 78 | } 79 | 80 | enum class HashAlgorithm : Serializable { 81 | SHA256, SHA384, SHA512 82 | } 83 | -------------------------------------------------------------------------------- /gradle-module-plugin/src/main/kotlin/io/ia/sdk/gradle/modl/task/Deploy.kt: -------------------------------------------------------------------------------- 1 | package io.ia.sdk.gradle.modl.task 2 | 3 | import org.gradle.api.DefaultTask 4 | import org.gradle.api.file.RegularFileProperty 5 | import org.gradle.api.model.ObjectFactory 6 | import org.gradle.api.provider.Property 7 | import org.gradle.api.tasks.Input 8 | import org.gradle.api.tasks.InputFile 9 | import org.gradle.api.tasks.Nested 10 | import org.gradle.api.tasks.TaskAction 11 | import org.gradle.api.tasks.options.Option 12 | import java.io.BufferedReader 13 | import java.io.DataInputStream 14 | import java.io.OutputStreamWriter 15 | import java.net.HttpURLConnection 16 | import java.net.HttpURLConnection.HTTP_CREATED 17 | import java.net.HttpURLConnection.HTTP_OK 18 | import java.net.URL 19 | import java.util.Base64 20 | 21 | // TODO finish implementation and tests, then register task with the plugin 22 | /** 23 | * Deploys the module to a running development gateway 24 | */ 25 | open class Deploy @javax.inject.Inject constructor(objects: ObjectFactory) : DefaultTask() { 26 | companion object { 27 | const val ID: String = "deployModl" 28 | const val SERVLET_PATH: String = "system/DeveloperModuleLoadingServlet" 29 | } 30 | 31 | init { 32 | this.mustRunAfter(":assemble") 33 | } 34 | 35 | @get:Input 36 | val hostGateway: Property = objects.property(String::class.java).convention("http://localhost:8088") 37 | 38 | @Option(option = "gateway", description = "Host ip for the development gateway, including protocol and port.") 39 | fun setGateway(url: String) { 40 | hostGateway.set(url) 41 | } 42 | 43 | @get:InputFile 44 | val module: RegularFileProperty = objects.fileProperty() 45 | 46 | @Option(option = "module", description = "Signed module file to be deployed to the running dev gateway") 47 | fun setModule(path: String) { 48 | module.set(project.file(path)) 49 | } 50 | 51 | @get:Nested 52 | val targetUrl: String by lazy { 53 | "${hostGateway.get()}/$SERVLET_PATH" 54 | } 55 | 56 | @TaskAction 57 | fun deployToGateway() { 58 | if (!module.isPresent) { 59 | logger.error( 60 | "Signed module property was not found. Run `assemble` first, or specify module path with " + 61 | "--module=/path/to/myfile.modl" 62 | ) 63 | throw Exception("Module file not present!") 64 | } 65 | 66 | val module = module.asFile.get() 67 | val b64 = Base64.getEncoder().encodeToString(module.readBytes()) 68 | 69 | // connect to gw 70 | val connection = connect(URL(targetUrl)) 71 | 72 | OutputStreamWriter(connection.outputStream).use { outstream -> 73 | outstream.write(b64) 74 | outstream.flush() 75 | } 76 | 77 | project.logger.quiet("Response code ${connection.responseCode}") 78 | 79 | if (connection.responseCode != HTTP_OK && connection.responseCode != HTTP_CREATED) { 80 | DataInputStream(connection.inputStream).use { instream -> 81 | BufferedReader(instream.reader(Charsets.UTF_8)).use { reader -> 82 | val output = reader.readText() 83 | logger.error("Error publishing module to gateway, $output") 84 | throw Exception("Could not post module to gateway, $output") 85 | } 86 | } 87 | } else { 88 | project.logger.quiet("Module ${module.name} was uploaded to ${hostGateway.get()}") 89 | } 90 | connection.disconnect() 91 | } 92 | 93 | fun connect(url: URL): HttpURLConnection { 94 | val connection = url.openConnection() as HttpURLConnection 95 | connection.requestMethod = "POST" 96 | connection.connectTimeout = 20000 97 | connection.doOutput = true 98 | connection.doInput = true 99 | connection.useCaches = false 100 | connection.setRequestProperty("Content-Type", "multipart/form-data") 101 | return connection 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /gradle-module-plugin/src/main/kotlin/io/ia/sdk/gradle/modl/task/ModuleBuildReport.kt: -------------------------------------------------------------------------------- 1 | package io.ia.sdk.gradle.modl.task 2 | 3 | import io.ia.sdk.gradle.modl.PLUGIN_TASK_GROUP 4 | import io.ia.sdk.gradle.modl.model.ArtifactManifest 5 | import io.ia.sdk.gradle.modl.model.AssemblyManifest 6 | import io.ia.sdk.gradle.modl.model.ChecksumResult 7 | import io.ia.sdk.gradle.modl.model.artifactManifestFromJson 8 | import io.ia.sdk.gradle.modl.model.jsonToChecksumResult 9 | import io.ia.sdk.gradle.modl.model.toJson 10 | import org.gradle.api.DefaultTask 11 | import org.gradle.api.file.ProjectLayout 12 | import org.gradle.api.file.RegularFile 13 | import org.gradle.api.file.RegularFileProperty 14 | import org.gradle.api.model.ObjectFactory 15 | import org.gradle.api.provider.MapProperty 16 | import org.gradle.api.provider.Property 17 | import org.gradle.api.provider.Provider 18 | import org.gradle.api.tasks.Input 19 | import org.gradle.api.tasks.InputFile 20 | import org.gradle.api.tasks.InputFiles 21 | import org.gradle.api.tasks.Internal 22 | import org.gradle.api.tasks.Optional 23 | import org.gradle.api.tasks.OutputFile 24 | import org.gradle.api.tasks.PathSensitive 25 | import org.gradle.api.tasks.PathSensitivity 26 | import org.gradle.api.tasks.TaskAction 27 | import javax.inject.Inject 28 | 29 | open class ModuleBuildReport @Inject constructor( 30 | objects: ObjectFactory, 31 | layout: ProjectLayout 32 | ) : DefaultTask() { 33 | 34 | companion object { 35 | const val ID = "modlReport" 36 | const val DEFAULT_REPORT_NAME = "buildResult.json" 37 | } 38 | 39 | /** 40 | * The name of the json file that contains build information about the module that was assembled. 41 | */ 42 | @get:Input 43 | val reportFileName: Property = objects.property(String::class.java).convention(DEFAULT_REPORT_NAME) 44 | 45 | /** 46 | * The report file that will be created by the task. 47 | */ 48 | @get:OutputFile 49 | val report: RegularFileProperty = objects.fileProperty().convention( 50 | layout.buildDirectory.file(reportFileName) 51 | ) 52 | 53 | /** 54 | * Map of arbitrary information, derived directly from the Map provided to the 55 | * [io.ia.sdk.gradle.modl.extension.ModuleSettings] extension object. These values are written to the json, 56 | * for convenient use by consumers of the reports, such as build automation/CI or reporting purposes. 57 | */ 58 | @get:Optional 59 | @get:Input 60 | val metaInfo: MapProperty = objects.mapProperty(String::class.java, String::class.java) 61 | 62 | /** 63 | * Json file containing the checksum information. 64 | */ 65 | @get:PathSensitive(PathSensitivity.RELATIVE) 66 | @get:InputFile 67 | val checksumJson: RegularFileProperty = objects.fileProperty() 68 | 69 | /** 70 | * The signed Ignition Module (modl file) 71 | */ 72 | @get:PathSensitive(PathSensitivity.RELATIVE) 73 | @get:InputFile 74 | val modlFile: RegularFileProperty = objects.fileProperty() 75 | 76 | /** 77 | * Module ID from the [io.ia.sdk.gradle.modl.extension.ModuleSettings] 78 | */ 79 | @get:Input 80 | val moduleId: Property = objects.property(String::class.java) 81 | 82 | /** 83 | * Module name from [io.ia.sdk.gradle.modl.extension.ModuleSettings.name] 84 | */ 85 | @get:Input 86 | val moduleName: Property = objects.property(String::class.java) 87 | 88 | /** 89 | * Description, from [io.ia.sdk.gradle.modl.extension.ModuleSettings.moduleDescription] 90 | */ 91 | @get:Input 92 | val moduleDescription: Property = objects.property(String::class.java) 93 | 94 | /** 95 | * Version of the module, based on the [io.ia.sdk.gradle.modl.extension.ModuleSettings.moduleVersion] 96 | */ 97 | @get:Input 98 | val moduleVersion: Property = objects.property(String::class.java) 99 | 100 | /** 101 | * Map of Gradle Project Path to the artifact manifest file for the subproject at that path. Internal, 102 | * as this is used to derive the actual inputs. 103 | */ 104 | @get:Internal 105 | val childManifests: MapProperty = objects.mapProperty( 106 | String::class.java, 107 | RegularFile::class.java 108 | ) 109 | 110 | /** 111 | * The artifact manifest files, pulled from the values of the child manifests above. Not directly used, but 112 | * included as input files to support 113 | */ 114 | @get:PathSensitive(PathSensitivity.RELATIVE) 115 | @get:InputFiles 116 | val artifactManifests: Provider> = childManifests.map { it.values.toSet() } 117 | 118 | @get:Input 119 | val childManifestJson: Provider> = childManifests.map { children -> 120 | children.mapValues { artifactManifestFromJson(it.value.asFile.readText()) } 121 | } 122 | 123 | init { 124 | group = PLUGIN_TASK_GROUP 125 | description = "Generates a json file in the root build directory containing module and build metadata." 126 | } 127 | 128 | @TaskAction 129 | fun execute() { 130 | // build manifest 131 | val checksum = if (checksumJson.isPresent && checksumJson.get().asFile.exists()) { 132 | checksumJson.get().asFile 133 | } else throw Exception("Checksum file did not exist at ${checksumJson.get().asFile}") 134 | 135 | val checksumResult: ChecksumResult = jsonToChecksumResult(checksum.readText()) 136 | 137 | val fileSize = modlFile.get().asFile.length() 138 | 139 | val manifest = AssemblyManifest( 140 | moduleId.get(), 141 | moduleName.get(), 142 | moduleDescription.get(), 143 | moduleVersion.get(), 144 | checksumResult, 145 | modlFile.get().asFile.name, 146 | fileSize, 147 | childManifestJson.get(), 148 | metaInfo.get() 149 | ) 150 | 151 | if (report.get().asFile.exists()) { 152 | report.get().asFile.delete() 153 | } 154 | 155 | report.get().asFile.writeText(manifest.toJson()) 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /gradle-module-plugin/src/main/kotlin/io/ia/sdk/gradle/modl/task/ZipModule.kt: -------------------------------------------------------------------------------- 1 | package io.ia.sdk.gradle.modl.task 2 | 3 | import io.ia.sdk.gradle.modl.PLUGIN_TASK_GROUP 4 | import org.gradle.api.DefaultTask 5 | import org.gradle.api.file.DirectoryProperty 6 | import org.gradle.api.file.RegularFileProperty 7 | import org.gradle.api.model.ObjectFactory 8 | import org.gradle.api.provider.Property 9 | import org.gradle.api.tasks.Input 10 | import org.gradle.api.tasks.InputDirectory 11 | import org.gradle.api.tasks.Optional 12 | import org.gradle.api.tasks.OutputFile 13 | import org.gradle.api.tasks.PathSensitive 14 | import org.gradle.api.tasks.PathSensitivity 15 | import org.gradle.api.tasks.TaskAction 16 | import javax.inject.Inject 17 | 18 | /** 19 | * Creates the unsigned .modl file by compressing the contents of the staging folder into a zip file. 20 | */ 21 | open class ZipModule @Inject constructor(objects: ObjectFactory) : DefaultTask() { 22 | companion object { 23 | const val ID = "zipModule" 24 | const val UNSIGNED_EXTENSION = "unsigned.modl" 25 | } 26 | 27 | @Optional 28 | @InputDirectory 29 | @PathSensitive(PathSensitivity.RELATIVE) 30 | val content: DirectoryProperty = objects.directoryProperty() 31 | 32 | @OutputFile 33 | val unsignedModule: RegularFileProperty = objects.fileProperty() 34 | 35 | @Input 36 | val moduleName: Property = objects.property(String::class.java) 37 | 38 | init { 39 | this.group = PLUGIN_TASK_GROUP 40 | this.description = 41 | "Packs the contents of the module folder into a zip archive with a .unsigned.modl file extension" 42 | } 43 | 44 | @TaskAction 45 | fun execute() { 46 | val unsignedFile = unsignedModule.get() 47 | val contentDir = content.get().asFile 48 | 49 | project.logger.info("Zipping '${contentDir.absolutePath}' into ' ${unsignedFile.asFile.absolutePath}'") 50 | project.ant.invokeMethod( 51 | "zip", 52 | mapOf("basedir" to contentDir, "destfile" to unsignedFile) 53 | ) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /gradle-module-plugin/src/main/kotlin/io/ia/sdk/gradle/modl/util/utilities.kt: -------------------------------------------------------------------------------- 1 | package io.ia.sdk.gradle.modl.util 2 | 3 | import io.ia.sdk.gradle.modl.IgnitionModlPlugin 4 | import org.gradle.api.Project 5 | 6 | fun Project.hasOptedOutOfModule(): Boolean { 7 | val propertyValue: String = 8 | this.properties.getOrDefault(IgnitionModlPlugin.PROJECT_OPT_OUT, "false").toString() 9 | 10 | return propertyValue == "true" 11 | } 12 | 13 | fun String.capitalize(): String { 14 | return this.replaceFirstChar { it.uppercase() } 15 | } 16 | -------------------------------------------------------------------------------- /gradle-module-plugin/src/test/kotlin/io/ia/sdk/gradle/modl/IgnitionModlPluginTest.kt: -------------------------------------------------------------------------------- 1 | package io.ia.sdk.gradle.modl 2 | 3 | import org.gradle.testfixtures.ProjectBuilder 4 | import org.junit.Test 5 | import kotlin.test.assertNotNull 6 | 7 | class IgnitionModlPluginTest { 8 | @Test 9 | fun `plugin applies without error task`() { 10 | // Create a test project and apply the plugin 11 | val project = ProjectBuilder.builder().build() 12 | project.plugins.apply("io.ia.sdk.modl") 13 | 14 | // Verify the result 15 | assertNotNull(project.tasks.findByName("assemble")) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inductiveautomation/ignition-module-tools/4fd236ab99605f165c610000eae27b3d13e60f92/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if %ERRORLEVEL% equ 0 goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if %ERRORLEVEL% equ 0 goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | set EXIT_CODE=%ERRORLEVEL% 84 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 85 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 86 | exit /b %EXIT_CODE% 87 | 88 | :mainEnd 89 | if "%OS%"=="Windows_NT" endlocal 90 | 91 | :omega 92 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | includeBuild("gradle-module-plugin") 2 | includeBuild("generator") 3 | 4 | rootProject.name = "ignition-module-tools" 5 | --------------------------------------------------------------------------------