├── .github └── workflows │ └── release.yaml ├── .gitignore ├── .idea ├── .gitignore ├── inspectionProfiles │ └── Project_Default.xml ├── kotlinc.xml ├── misc.xml └── vcs.xml ├── LICENSE ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts └── src └── main └── kotlin └── com └── rickclephas └── kmp └── docc ├── KotlinDocCPlugin.kt ├── dsl └── DocCTasks.kt └── tasks ├── DocCConvertTask.kt ├── DocCPreviewTask.kt ├── DocCTask.kt ├── DownloadDocCRenderTask.kt └── internal ├── CreateDocCSourceBundleTask.kt ├── ExtractObjCSymbolGraphTask.kt ├── ExtractSwiftSymbolGraphTask.kt ├── ExtractSymbolGraphTask.kt ├── GetKotlinDocCRenderUrlTask.kt └── Utils.kt /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Publish a release 2 | on: 3 | push: 4 | tags: 5 | - 'v[0-9]+.[0-9]+.[0-9]+' 6 | jobs: 7 | publish-to-sonatype: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v3 12 | - name: Gradle Wrapper Validation 13 | uses: gradle/wrapper-validation-action@v1 14 | - name: Setup JDK 15 | uses: actions/setup-java@v3 16 | with: 17 | distribution: 'zulu' 18 | java-version: '17' 19 | - name: Setup Gradle 20 | uses: gradle/gradle-build-action@v2 21 | - name: Publish to Sonatype 22 | run: ./gradlew publishAllPublicationsToSonatypeRepository 23 | env: 24 | SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }} 25 | SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} 26 | SIGNING_SECRET_KEY: ${{ secrets.SIGNING_SECRET_KEY }} 27 | OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} 28 | OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | local.properties 2 | 3 | # Created by https://www.toptal.com/developers/gitignore/api/gradle,kotlin,intellij 4 | # Edit at https://www.toptal.com/developers/gitignore?templates=gradle,kotlin,intellij 5 | 6 | ### Intellij ### 7 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 8 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 9 | 10 | # User-specific stuff 11 | .idea/**/workspace.xml 12 | .idea/**/tasks.xml 13 | .idea/**/usage.statistics.xml 14 | .idea/**/dictionaries 15 | .idea/**/shelf 16 | 17 | # AWS User-specific 18 | .idea/**/aws.xml 19 | 20 | # Generated files 21 | .idea/**/contentModel.xml 22 | 23 | # Sensitive or high-churn files 24 | .idea/**/dataSources/ 25 | .idea/**/dataSources.ids 26 | .idea/**/dataSources.local.xml 27 | .idea/**/sqlDataSources.xml 28 | .idea/**/dynamic.xml 29 | .idea/**/uiDesigner.xml 30 | .idea/**/dbnavigator.xml 31 | 32 | # Gradle 33 | .idea/**/gradle.xml 34 | .idea/**/libraries 35 | 36 | # Gradle and Maven with auto-import 37 | # When using Gradle or Maven with auto-import, you should exclude module files, 38 | # since they will be recreated, and may cause churn. Uncomment if using 39 | # auto-import. 40 | .idea/artifacts 41 | .idea/compiler.xml 42 | .idea/jarRepositories.xml 43 | .idea/modules.xml 44 | .idea/*.iml 45 | .idea/modules 46 | *.iml 47 | *.ipr 48 | 49 | # CMake 50 | cmake-build-*/ 51 | 52 | # Mongo Explorer plugin 53 | .idea/**/mongoSettings.xml 54 | 55 | # File-based project format 56 | *.iws 57 | 58 | # IntelliJ 59 | out/ 60 | 61 | # mpeltonen/sbt-idea plugin 62 | .idea_modules/ 63 | 64 | # JIRA plugin 65 | atlassian-ide-plugin.xml 66 | 67 | # Cursive Clojure plugin 68 | .idea/replstate.xml 69 | 70 | # SonarLint plugin 71 | .idea/sonarlint/ 72 | 73 | # Crashlytics plugin (for Android Studio and IntelliJ) 74 | com_crashlytics_export_strings.xml 75 | crashlytics.properties 76 | crashlytics-build.properties 77 | fabric.properties 78 | 79 | # Editor-based Rest Client 80 | .idea/httpRequests 81 | 82 | # Android studio 3.1+ serialized cache file 83 | .idea/caches/build_file_checksums.ser 84 | 85 | ### Intellij Patch ### 86 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 87 | 88 | # *.iml 89 | # modules.xml 90 | # .idea/misc.xml 91 | # *.ipr 92 | 93 | # Sonarlint plugin 94 | # https://plugins.jetbrains.com/plugin/7973-sonarlint 95 | .idea/**/sonarlint/ 96 | 97 | # SonarQube Plugin 98 | # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin 99 | .idea/**/sonarIssues.xml 100 | 101 | # Markdown Navigator plugin 102 | # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced 103 | .idea/**/markdown-navigator.xml 104 | .idea/**/markdown-navigator-enh.xml 105 | .idea/**/markdown-navigator/ 106 | 107 | # Cache file creation bug 108 | # See https://youtrack.jetbrains.com/issue/JBR-2257 109 | .idea/$CACHE_FILE$ 110 | 111 | # CodeStream plugin 112 | # https://plugins.jetbrains.com/plugin/12206-codestream 113 | .idea/codestream.xml 114 | 115 | # Azure Toolkit for IntelliJ plugin 116 | # https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij 117 | .idea/**/azureSettings.xml 118 | 119 | ### Kotlin ### 120 | # Compiled class file 121 | *.class 122 | 123 | # Log file 124 | *.log 125 | 126 | # BlueJ files 127 | *.ctxt 128 | 129 | # Mobile Tools for Java (J2ME) 130 | .mtj.tmp/ 131 | 132 | # Package Files # 133 | *.jar 134 | *.war 135 | *.nar 136 | *.ear 137 | *.zip 138 | *.tar.gz 139 | *.rar 140 | 141 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 142 | hs_err_pid* 143 | replay_pid* 144 | 145 | ### Gradle ### 146 | .gradle 147 | **/build/ 148 | !src/**/build/ 149 | 150 | # Ignore Gradle GUI config 151 | gradle-app.setting 152 | 153 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 154 | !gradle-wrapper.jar 155 | 156 | # Avoid ignore Gradle wrappper properties 157 | !gradle-wrapper.properties 158 | 159 | # Cache of project 160 | .gradletasknamecache 161 | 162 | # Eclipse Gradle plugin generated files 163 | # Eclipse Core 164 | .project 165 | # JDT-specific (Eclipse Java Development Tools) 166 | .classpath 167 | 168 | ### Gradle Patch ### 169 | # Java heap dump 170 | *.hprof 171 | 172 | # End of https://www.toptal.com/developers/gitignore/api/gradle,kotlin,intellij 173 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Rick Clephas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kotlin-DocC 2 | 3 | Kotlin-DocC is a Gradle plugin that integrates the [`docc`](https://github.com/apple/swift-docc) 4 | CLI into your Kotlin Multiplatform projects. 5 | 6 | Generating your first `.doccarchive` is as easy as applying the plugin: 7 | 8 | ```kotlin 9 | plugins { 10 | id("com.rickclephas.kmp.docc") version "0.1.0" 11 | } 12 | ``` 13 | 14 | and running the `doccConvert` or `doccPreview` task for your target's framework. 15 | 16 | Both the `doccConvert` and `doccPreview` tasks can be configured with the `docC` extension: 17 | 18 | ```kotlin 19 | import com.rickclephas.kmp.docc.dsl.docC 20 | 21 | group = "com.example" 22 | version = "1.0" 23 | 24 | kotlin { 25 | macosArm64 { 26 | binaries.framework { 27 | baseName = "MyFramework" 28 | 29 | docC { 30 | // Configure the doccConvert and doccPreview tasks 31 | common { 32 | // Configure the fallback values for the documentation bundle's Info.plist properties 33 | displayName.set("MyFramework") 34 | bundleIdentifier.set("com.example.MyFramework") 35 | bundleVersion.set("1.0") 36 | 37 | // Use a custom Swift-DocC-Render template, 38 | // by default the Kotlin-DocC-Render template is used. 39 | // Setting this to `null` wil use the built-in Xcode template. 40 | renderDir.set(null as Directory?) 41 | 42 | // Adds additional arguments to the docc command 43 | additionalArgs.add("--warnings-as-errors") 44 | } 45 | 46 | // Configure the doccConvert task 47 | convert { 48 | // Indicates if the `process-archive index` command should be run 49 | createIndex.set(false) 50 | } 51 | 52 | // Configure the doccPreview task 53 | preview { 54 | // The port number to use for the preview web server 55 | port.set(8080) 56 | } 57 | } 58 | } 59 | } 60 | } 61 | ``` 62 | 63 | To further customize your documentation you can generate a `.docc` documentation bundle by adding files to 64 | `src/commonMain/docc`, or the `docc` folder of other source-sets. 65 | 66 | Take a look at the official DocC [documentation](https://www.swift.org/documentation/docc) to learn more about 67 | [formatting your documentation content](https://www.swift.org/documentation/docc/formatting-your-documentation-content) 68 | and [adding structure to your documentation pages](https://www.swift.org/documentation/docc/adding-structure-to-your-documentation-pages). 69 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("UnstableApiUsage") 2 | 3 | import java.util.Properties 4 | 5 | plugins { 6 | `java-gradle-plugin` 7 | alias(libs.plugins.kotlin.jvm) 8 | `maven-publish` 9 | signing 10 | } 11 | 12 | group = "com.rickclephas.kmp" 13 | version = "0.2.0" 14 | 15 | kotlin { 16 | explicitApi() 17 | jvmToolchain(11) 18 | } 19 | 20 | java { 21 | withJavadocJar() 22 | withSourcesJar() 23 | } 24 | 25 | gradlePlugin { 26 | website = "https://github.com/rickclephas/kotlin-docc" 27 | vcsUrl = "https://github.com/rickclephas/kotlin-docc" 28 | plugins { 29 | create("kotlin-docc") { 30 | id = "com.rickclephas.kmp.docc" 31 | displayName = "kotlin-docc" 32 | description = "Swift DocC documentation for Kotlin Multiplatform frameworks" 33 | implementationClass = "com.rickclephas.kmp.docc.KotlinDocCPlugin" 34 | tags = listOf("kotlin", "swift", "documentation") 35 | } 36 | } 37 | } 38 | 39 | repositories { 40 | mavenCentral() 41 | } 42 | 43 | dependencies { 44 | implementation(libs.kotlin.gradle.plugin) 45 | testImplementation(kotlin("test")) 46 | } 47 | 48 | //region Publishing 49 | 50 | ext["signing.keyId"] = null 51 | ext["signing.password"] = null 52 | ext["signing.secretKey"] = null 53 | ext["signing.secretKeyRingFile"] = null 54 | ext["ossrhUsername"] = null 55 | ext["ossrhPassword"] = null 56 | val localPropsFile = project.rootProject.file("local.properties") 57 | if (localPropsFile.exists()) { 58 | localPropsFile.reader() 59 | .use { Properties().apply { load(it) } } 60 | .onEach { (name, value) -> ext[name.toString()] = value } 61 | } else { 62 | ext["signing.keyId"] = System.getenv("SIGNING_KEY_ID") 63 | ext["signing.password"] = System.getenv("SIGNING_PASSWORD") 64 | ext["signing.secretKey"] = System.getenv("SIGNING_SECRET_KEY") 65 | ext["signing.secretKeyRingFile"] = System.getenv("SIGNING_SECRET_KEY_RING_FILE") 66 | ext["ossrhUsername"] = System.getenv("OSSRH_USERNAME") 67 | ext["ossrhPassword"] = System.getenv("OSSRH_PASSWORD") 68 | } 69 | 70 | fun getExtraString(name: String) = ext[name]?.toString() 71 | 72 | val signPublications = getExtraString("signing.keyId") != null 73 | 74 | publishing { 75 | repositories { 76 | maven { 77 | name = "sonatype" 78 | setUrl("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/") 79 | credentials { 80 | username = getExtraString("ossrhUsername") 81 | password = getExtraString("ossrhPassword") 82 | } 83 | } 84 | } 85 | 86 | publications.withType { 87 | if (signPublications) signing.sign(this) 88 | 89 | pom { 90 | name = "kotlin-docc" 91 | description = "Swift DocC documentation for Kotlin Multiplatform frameworks" 92 | url = "https://github.com/rickclephas/kotlin-docc" 93 | licenses { 94 | license { 95 | name = "MIT" 96 | url = "https://opensource.org/licenses/MIT" 97 | } 98 | } 99 | developers { 100 | developer { 101 | id = "rickclephas" 102 | name = "Rick Clephas" 103 | email = "rclephas@gmail.com" 104 | } 105 | } 106 | scm { 107 | url = "https://github.com/rickclephas/kotlin-docc" 108 | } 109 | } 110 | } 111 | } 112 | 113 | if (signPublications) { 114 | signing { 115 | getExtraString("signing.secretKey")?.let { secretKey -> 116 | useInMemoryPgpKeys(getExtraString("signing.keyId"), secretKey, getExtraString("signing.password")) 117 | } 118 | } 119 | } 120 | 121 | //endregion 122 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | kotlin = "1.9.20-RC" 3 | 4 | [libraries] 5 | kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } 6 | 7 | [plugins] 8 | kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } 9 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickclephas/kotlin-docc/c4ae3fe8fe6e911080ab7406ebf3618675266f9f/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-8.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | mavenCentral() 4 | gradlePluginPortal() 5 | } 6 | } 7 | 8 | plugins { 9 | id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" 10 | } 11 | 12 | rootProject.name = "kotlin-docc" 13 | -------------------------------------------------------------------------------- /src/main/kotlin/com/rickclephas/kmp/docc/KotlinDocCPlugin.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.docc 2 | 3 | import com.rickclephas.kmp.docc.tasks.DocCConvertTask.Companion.doccConvertTask 4 | import com.rickclephas.kmp.docc.tasks.DocCPreviewTask.Companion.doccPreviewTask 5 | import org.gradle.api.Plugin 6 | import org.gradle.api.Project 7 | import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension 8 | import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation 9 | import org.jetbrains.kotlin.gradle.plugin.mpp.Framework 10 | import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget 11 | 12 | public class KotlinDocCPlugin: Plugin { 13 | 14 | private companion object { 15 | const val kotlinPluginId = "org.jetbrains.kotlin.multiplatform" 16 | } 17 | 18 | override fun apply(target: Project) { 19 | target.pluginManager.withPlugin(kotlinPluginId) { 20 | val kotlin = target.extensions.getByType(KotlinMultiplatformExtension::class.java) 21 | kotlin.targets.withType(KotlinNativeTarget::class.java).configureEach { kotlinNativeTarget -> 22 | kotlinNativeTarget.compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME).apply { 23 | kotlinOptions.freeCompilerArgs += "-Xexport-kdoc" 24 | } 25 | kotlinNativeTarget.binaries.withType(Framework::class.java).configureEach { framework -> 26 | framework.doccConvertTask 27 | framework.doccPreviewTask 28 | } 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/kotlin/com/rickclephas/kmp/docc/dsl/DocCTasks.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.docc.dsl 2 | 3 | import com.rickclephas.kmp.docc.tasks.DocCConvertTask 4 | import com.rickclephas.kmp.docc.tasks.DocCConvertTask.Companion.doccConvertTask 5 | import com.rickclephas.kmp.docc.tasks.DocCPreviewTask 6 | import com.rickclephas.kmp.docc.tasks.DocCPreviewTask.Companion.doccPreviewTask 7 | import com.rickclephas.kmp.docc.tasks.DocCTask 8 | import org.gradle.api.Action 9 | import org.jetbrains.kotlin.gradle.plugin.mpp.Framework 10 | 11 | public class DocCTasks internal constructor(framework: Framework) { 12 | 13 | internal val convertTask = framework.doccConvertTask 14 | public fun convert(configure: DocCConvertTask.() -> Unit): Unit = convertTask.configure { it.configure() } 15 | public fun convert(configure: Action): Unit = convert { configure.execute(this) } 16 | 17 | internal val previewTask = framework.doccPreviewTask 18 | public fun preview(configure: DocCPreviewTask.() -> Unit): Unit = previewTask.configure { it.configure() } 19 | public fun preview(configure: Action): Unit = preview { configure.execute(this) } 20 | 21 | public fun common(configure: DocCTask.() -> Unit) { 22 | convert(configure) 23 | preview(configure) 24 | } 25 | public fun common(configure: Action) { 26 | convert { configure.execute(this) } 27 | preview { configure.execute(this) } 28 | } 29 | } 30 | 31 | public val Framework.docC: DocCTasks get() = DocCTasks(this) 32 | public fun Framework.docC(configure: DocCTasks.() -> Unit): DocCTasks = docC.apply(configure) 33 | public fun Framework.docC(configure: Action): DocCTasks = docC { configure.execute(this) } 34 | -------------------------------------------------------------------------------- /src/main/kotlin/com/rickclephas/kmp/docc/tasks/DocCConvertTask.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.docc.tasks 2 | 3 | import com.rickclephas.kmp.docc.tasks.internal.locateOrRegister 4 | import com.rickclephas.kmp.docc.tasks.internal.taskSuffix 5 | import org.gradle.api.provider.Property 6 | import org.gradle.api.tasks.* 7 | import org.jetbrains.kotlin.gradle.plugin.mpp.Framework 8 | import javax.inject.Inject 9 | 10 | @CacheableTask 11 | public abstract class DocCConvertTask @Inject constructor( 12 | framework: Framework 13 | ): DocCTask(framework, "convert") { 14 | 15 | internal companion object { 16 | val Framework.doccConvertTask: TaskProvider 17 | get() = project.tasks.locateOrRegister( 18 | "doccConvert${taskSuffix}", 19 | DocCConvertTask::class.java, 20 | this 21 | ) 22 | } 23 | 24 | init { 25 | description = "Generates a .doccarchive for framework '${framework.name}' with target '${framework.target.targetName}'" 26 | } 27 | 28 | /** 29 | * Indicates if an index should be created for the produced `.doccarchive`, defaults to `false`. 30 | */ 31 | @get:Input 32 | public val createIndex: Property = project.objects.property(Boolean::class.java).convention(false) 33 | 34 | override fun exec() { 35 | super.exec() 36 | if (!createIndex.get()) return 37 | execOperations.exec { 38 | it.executable = "/usr/bin/xcrun" 39 | it.args("docc", "process-archive", "index", 40 | outputDirectory.get().asFile.absolutePath, 41 | "--bundle-identifier", bundleIdentifier.get(), 42 | ) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/kotlin/com/rickclephas/kmp/docc/tasks/DocCPreviewTask.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.docc.tasks 2 | 3 | import com.rickclephas.kmp.docc.tasks.internal.locateOrRegister 4 | import com.rickclephas.kmp.docc.tasks.internal.taskSuffix 5 | import org.gradle.api.provider.Property 6 | import org.gradle.api.tasks.* 7 | import org.gradle.work.DisableCachingByDefault 8 | import org.jetbrains.kotlin.gradle.plugin.mpp.Framework 9 | import javax.inject.Inject 10 | 11 | @DisableCachingByDefault 12 | public abstract class DocCPreviewTask @Inject constructor( 13 | framework: Framework 14 | ): DocCTask(framework, "preview") { 15 | 16 | internal companion object { 17 | val Framework.doccPreviewTask: TaskProvider 18 | get() = project.tasks.locateOrRegister( 19 | "doccPreview${taskSuffix}", 20 | DocCPreviewTask::class.java, 21 | this 22 | ) 23 | } 24 | 25 | /** 26 | * Port number passed to `--port`, defaults to `8080`. 27 | */ 28 | @get:Input 29 | public val port: Property = project.objects.property(Int::class.java).convention(8080) 30 | 31 | init { 32 | description = "Previews DocC documentation for framework '${framework.name}' with target '${framework.target.targetName}'" 33 | additionalArgs.addAll(port.map { listOf("--port", it.toString()) }) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/kotlin/com/rickclephas/kmp/docc/tasks/DocCTask.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.docc.tasks 2 | 3 | import com.rickclephas.kmp.docc.tasks.DownloadDocCRenderTask.Companion.downloadDoccRenderTask 4 | import com.rickclephas.kmp.docc.tasks.internal.* 5 | import com.rickclephas.kmp.docc.tasks.internal.CreateDocCSourceBundleTask.Companion.createDoccSourceBundleTask 6 | import com.rickclephas.kmp.docc.tasks.internal.ExtractObjCSymbolGraphTask.Companion.extractObjcSymbolGraphTask 7 | import com.rickclephas.kmp.docc.tasks.internal.ExtractSwiftSymbolGraphTask.Companion.extractSwiftSymbolGraph 8 | import com.rickclephas.kmp.docc.tasks.internal.baseNameProvider 9 | import org.gradle.api.DefaultTask 10 | import org.gradle.api.file.Directory 11 | import org.gradle.api.file.DirectoryProperty 12 | import org.gradle.api.plugins.JavaBasePlugin 13 | import org.gradle.api.provider.ListProperty 14 | import org.gradle.api.provider.Property 15 | import org.gradle.api.provider.Provider 16 | import org.gradle.api.tasks.* 17 | import org.gradle.process.ExecOperations 18 | import org.gradle.work.DisableCachingByDefault 19 | import org.jetbrains.kotlin.gradle.plugin.mpp.Framework 20 | import org.jetbrains.kotlin.konan.target.HostManager 21 | import javax.inject.Inject 22 | 23 | @DisableCachingByDefault 24 | @Suppress("LeakingThis") 25 | public abstract class DocCTask( 26 | @get:Internal 27 | @Transient 28 | public val framework: Framework, 29 | private val subcommand: String 30 | ): DefaultTask() { 31 | 32 | init { 33 | onlyIf { HostManager.hostIsMac } 34 | group = JavaBasePlugin.DOCUMENTATION_GROUP 35 | dependsOn(framework.extractObjcSymbolGraphTask) 36 | dependsOn(framework.extractSwiftSymbolGraph) 37 | } 38 | 39 | @get:Inject 40 | @get:Internal 41 | protected abstract val execOperations: ExecOperations 42 | 43 | @get:Input 44 | protected val projectGroup: Provider = project.provider { project.group.toString() } 45 | 46 | @get:Input 47 | protected val projectVersion: Provider = project.provider { project.version.toString() } 48 | 49 | @get:Input 50 | public val baseName: Provider = framework.baseNameProvider 51 | 52 | /** 53 | * The `.docc` source bundle used to build the `.doccarchive`. 54 | */ 55 | @get:InputDirectory 56 | @get:PathSensitive(PathSensitivity.ABSOLUTE) 57 | public val sourceBundle: DirectoryProperty = project.objects.directoryProperty() 58 | .convention(framework.target.createDoccSourceBundleTask.flatMap { it.outputDirectory }) 59 | 60 | @get:InputDirectory 61 | @get:PathSensitive(PathSensitivity.ABSOLUTE) 62 | public val symbolGraphDir: Provider = framework.symbolGraphDir 63 | 64 | /** 65 | * The directory containing the Swift-DocC-Render distribution, or `null` to use the Xcode default. 66 | */ 67 | @get:Optional 68 | @get:InputDirectory 69 | @get:PathSensitive(PathSensitivity.ABSOLUTE) 70 | public val renderDir: DirectoryProperty = project.objects.directoryProperty() 71 | .value(project.downloadDoccRenderTask.flatMap { it.outputDirectory }) 72 | 73 | /** 74 | * The `.doccarchive` directory. 75 | */ 76 | @get:OutputDirectory 77 | public val outputDirectory: DirectoryProperty = project.objects.directoryProperty() 78 | .convention(framework.doccArchiveDir) 79 | 80 | /** 81 | * The display name passed to `--fallback-display-name`, defaults to the `baseName`. 82 | */ 83 | @get:Input 84 | public val displayName: Property = project.objects.property(String::class.java) 85 | .convention(baseName) 86 | 87 | /** 88 | * The bundle identifier passed to `--bundle-identifier` and `--fallback-bundle-identifier`, 89 | * defaults to `${project.group}.$baseName`. 90 | */ 91 | @get:Input 92 | public val bundleIdentifier: Property = project.objects.property(String::class.java) 93 | .convention(baseName.flatMap { baseName -> projectGroup.map { "$it.$baseName" } }) 94 | 95 | /** 96 | * The bundle version passed to `--fallback-bundle-version`, defaults to `${project.version}`. 97 | */ 98 | @get:Input 99 | public val bundleVersion: Property = project.objects.property(String::class.java) 100 | .convention(projectVersion) 101 | 102 | /** 103 | * Additional DocC arguments that will be provided to the subcommand. 104 | */ 105 | @get:Input 106 | public val additionalArgs: ListProperty = project.objects.listProperty(String::class.java) 107 | 108 | @TaskAction 109 | public open fun exec() { 110 | execOperations.exec { 111 | renderDir.orNull?.let { renderDir -> 112 | it.environment("DOCC_HTML_DIR", renderDir.asFile.absolutePath) 113 | } 114 | it.executable = "/usr/bin/xcrun" 115 | it.args("docc", subcommand, 116 | "--additional-symbol-graph-dir", symbolGraphDir.get().asFile.absolutePath, 117 | "--output-path", outputDirectory.get().asFile.absolutePath, 118 | "--fallback-display-name", displayName.get(), 119 | "--fallback-bundle-identifier", bundleIdentifier.get(), 120 | "--fallback-bundle-version", bundleVersion.get(), 121 | ) 122 | it.args(additionalArgs.get()) 123 | it.args(sourceBundle.get().asFile.absolutePath) 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/main/kotlin/com/rickclephas/kmp/docc/tasks/DownloadDocCRenderTask.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.docc.tasks 2 | 3 | import com.rickclephas.kmp.docc.tasks.internal.GetKotlinDocCRenderUrlTask.Companion.getKotlinDoccRenderUrlTask 4 | import com.rickclephas.kmp.docc.tasks.internal.locateOrRegister 5 | import org.gradle.api.DefaultTask 6 | import org.gradle.api.Project 7 | import org.gradle.api.file.DirectoryProperty 8 | import org.gradle.api.file.FileSystemOperations 9 | import org.gradle.api.provider.Property 10 | import org.gradle.api.tasks.* 11 | import java.net.URL 12 | import java.nio.channels.Channels 13 | import javax.inject.Inject 14 | 15 | @CacheableTask 16 | public abstract class DownloadDocCRenderTask: DefaultTask() { 17 | 18 | internal companion object { 19 | val Project.downloadDoccRenderTask: TaskProvider 20 | get() = tasks.locateOrRegister("downloadDoccRender", DownloadDocCRenderTask::class.java) 21 | } 22 | 23 | @get:Inject 24 | protected abstract val fileSystemOperations: FileSystemOperations 25 | 26 | /** 27 | * The URL of the DocC-Render distribution ZIP that should be downloaded. 28 | */ 29 | @get:Input 30 | public val downloadUrl: Property = project.objects.property(String::class.java) 31 | .convention(project.getKotlinDoccRenderUrlTask.flatMap { it.outputFile }.map { it.asFile.readText() }) 32 | 33 | private val downloadFile = project.layout.buildDirectory.file("docc/render.zip") 34 | 35 | /** 36 | * The directory where the DoC-Render distribution should be stored. 37 | */ 38 | @get:OutputDirectory 39 | public val outputDirectory: DirectoryProperty = project.objects.directoryProperty() 40 | .convention(project.layout.buildDirectory.dir("docc/render")) 41 | 42 | @TaskAction 43 | public fun download() { 44 | val downloadUrl = URL(downloadUrl.get()) 45 | val downloadFile = downloadFile.get().asFile 46 | downloadFile.outputStream().channel.transferFrom(Channels.newChannel(downloadUrl.openStream()), 0, Long.MAX_VALUE) 47 | fileSystemOperations.sync { 48 | it.from(project.zipTree(downloadFile)) 49 | it.into(outputDirectory.get()) 50 | } 51 | downloadFile.delete() 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/kotlin/com/rickclephas/kmp/docc/tasks/internal/CreateDocCSourceBundleTask.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.docc.tasks.internal 2 | 3 | import org.gradle.api.DefaultTask 4 | import org.gradle.api.file.DirectoryProperty 5 | import org.gradle.api.file.DuplicatesStrategy 6 | import org.gradle.api.file.FileSystemOperations 7 | import org.gradle.api.file.FileTree 8 | import org.gradle.api.tasks.* 9 | import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation 10 | import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget 11 | import org.jetbrains.kotlin.konan.target.HostManager 12 | import javax.inject.Inject 13 | 14 | @CacheableTask 15 | @Suppress("LeakingThis") 16 | internal abstract class CreateDocCSourceBundleTask @Inject constructor( 17 | @get:Internal 18 | @Transient 19 | val target: KotlinNativeTarget 20 | ): DefaultTask() { 21 | 22 | internal companion object { 23 | val KotlinNativeTarget.createDoccSourceBundleTask: TaskProvider 24 | get() = project.tasks.locateOrRegister( 25 | "createDoccSourceBundle${taskSuffix}", 26 | CreateDocCSourceBundleTask::class.java, 27 | this 28 | ) 29 | } 30 | 31 | private val sourceFiles = project.objects.fileCollection() 32 | 33 | @get:InputFiles 34 | @PathSensitive(PathSensitivity.ABSOLUTE) 35 | val source: FileTree = sourceFiles.asFileTree 36 | 37 | @get:Inject 38 | abstract val fileSystemOperations: FileSystemOperations 39 | 40 | @get:OutputDirectory 41 | val outputDirectory: DirectoryProperty = project.objects.directoryProperty() 42 | .convention(target.sourceBundleDir) 43 | 44 | init { 45 | onlyIf { HostManager.hostIsMac } 46 | target.compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME).allKotlinSourceSets.forAll { 47 | sourceFiles.from("src/${it.name}/docc") 48 | } 49 | } 50 | 51 | @TaskAction 52 | fun action() { 53 | fileSystemOperations.sync { 54 | it.from(source) 55 | it.into(outputDirectory) 56 | it.duplicatesStrategy = DuplicatesStrategy.FAIL 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/kotlin/com/rickclephas/kmp/docc/tasks/internal/ExtractObjCSymbolGraphTask.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.docc.tasks.internal 2 | 3 | import org.gradle.api.tasks.* 4 | import org.jetbrains.kotlin.gradle.plugin.mpp.Framework 5 | import javax.inject.Inject 6 | 7 | @CacheableTask 8 | internal abstract class ExtractObjCSymbolGraphTask @Inject constructor( 9 | framework: Framework 10 | ): ExtractSymbolGraphTask(framework, "objc") { 11 | 12 | internal companion object { 13 | val Framework.extractObjcSymbolGraphTask: TaskProvider 14 | get() = project.tasks.locateOrRegister( 15 | "extractObjcSymbolGraph${taskSuffix}", 16 | ExtractObjCSymbolGraphTask::class.java, 17 | this 18 | ) 19 | } 20 | 21 | override fun extract() { 22 | super.extract() 23 | val sdkPath = getSdkPath() 24 | val headersDir = frameworkDir.get().dir("Headers") 25 | val baseName = baseName.get() 26 | execOperations.exec { exec -> 27 | exec.workingDir = headersDir.asFile 28 | exec.executable = "/usr/bin/xcrun" 29 | exec.args("clang", "-extract-api", 30 | "--product-name=$baseName", 31 | "-o", outputDirectory.get().file("$baseName.symbols.json").asFile.absolutePath, 32 | "-isysroot", sdkPath, 33 | "-F", "$sdkPath/System/Library/Frameworks", 34 | "-I", "./", 35 | "-x", "objective-c-header", 36 | ) 37 | headersDir.asFileTree.matching { 38 | it.include("*.h") 39 | }.forEach { 40 | exec.args(it.name) 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/kotlin/com/rickclephas/kmp/docc/tasks/internal/ExtractSwiftSymbolGraphTask.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.docc.tasks.internal 2 | 3 | import org.gradle.api.tasks.* 4 | import org.jetbrains.kotlin.gradle.plugin.mpp.Framework 5 | import org.jetbrains.kotlin.konan.target.KonanTarget 6 | import javax.inject.Inject 7 | 8 | @CacheableTask 9 | internal abstract class ExtractSwiftSymbolGraphTask @Inject constructor( 10 | framework: Framework 11 | ): ExtractSymbolGraphTask(framework, "swift") { 12 | 13 | internal companion object { 14 | val Framework.extractSwiftSymbolGraph: TaskProvider 15 | get() = project.tasks.locateOrRegister( 16 | "extractSwiftSymbolGraph${taskSuffix}", 17 | ExtractSwiftSymbolGraphTask::class.java, 18 | this 19 | ) 20 | } 21 | 22 | override fun extract() { 23 | super.extract() 24 | val sdkPath = getSdkPath() 25 | val target = when (konanTarget) { 26 | is KonanTarget.IOS_ARM64 -> "arm64-apple-ios" 27 | is KonanTarget.IOS_ARM32 -> "armv7-apple-ios" 28 | is KonanTarget.IOS_SIMULATOR_ARM64 -> "arm64-apple-ios-simulator" 29 | is KonanTarget.IOS_X64 -> "x86_64-apple-ios-simulator" 30 | is KonanTarget.WATCHOS_ARM32 -> "armv7k-apple-watchos" 31 | is KonanTarget.WATCHOS_ARM64 -> "arm64_32-apple-watchos" 32 | is KonanTarget.WATCHOS_DEVICE_ARM64 -> "arm64-apple-watchos" 33 | is KonanTarget.WATCHOS_X64 -> "x86_64-apple-watchos-simulator" 34 | is KonanTarget.WATCHOS_SIMULATOR_ARM64 -> "arm64-apple-watchos-simulator" 35 | is KonanTarget.WATCHOS_X86 -> "i386-apple-watchos-simulator" 36 | is KonanTarget.TVOS_ARM64 -> "arm64-apple-tvos" 37 | is KonanTarget.TVOS_X64 -> "x86_64-apple-tvos-simulator" 38 | is KonanTarget.TVOS_SIMULATOR_ARM64 -> "arm64-apple-tvos-simulator" 39 | is KonanTarget.MACOS_X64 -> "x86_64-apple-macos" 40 | is KonanTarget.MACOS_ARM64 -> "arm64-apple-macos" 41 | else -> error("Unsupported target: ${konanTarget.name}") 42 | } 43 | execOperations.exec { exec -> 44 | exec.executable = "/usr/bin/xcrun" 45 | exec.args("swift-symbolgraph-extract", 46 | "-sdk", sdkPath, 47 | "-target", target, 48 | "-F", frameworkDir.get().asFile.parent, 49 | "-module-name", baseName.get(), 50 | "-output-dir", outputDirectory.get().asFile.absolutePath, 51 | ) 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/kotlin/com/rickclephas/kmp/docc/tasks/internal/ExtractSymbolGraphTask.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.docc.tasks.internal 2 | 3 | import org.gradle.api.DefaultTask 4 | import org.gradle.api.file.Directory 5 | import org.gradle.api.provider.Provider 6 | import org.gradle.api.tasks.* 7 | import org.gradle.process.ExecOperations 8 | import org.gradle.work.DisableCachingByDefault 9 | import org.jetbrains.kotlin.gradle.plugin.mpp.Framework 10 | import org.jetbrains.kotlin.konan.target.HostManager 11 | import org.jetbrains.kotlin.konan.target.KonanTarget 12 | import java.io.ByteArrayOutputStream 13 | import javax.inject.Inject 14 | 15 | @DisableCachingByDefault 16 | @Suppress("LeakingThis") 17 | internal abstract class ExtractSymbolGraphTask( 18 | @get:Internal 19 | @Transient 20 | val framework: Framework, 21 | private val language: String, 22 | ): DefaultTask() { 23 | 24 | init { 25 | onlyIf { HostManager.hostIsMac } 26 | dependsOn(framework.linkTaskProvider) 27 | } 28 | 29 | @Internal 30 | protected val konanTarget = framework.target.konanTarget 31 | 32 | @get:Inject 33 | @get:Internal 34 | protected abstract val execOperations: ExecOperations 35 | 36 | @get:Input 37 | val baseName: Provider = framework.baseNameProvider 38 | 39 | @get:InputDirectory 40 | @get:PathSensitive(PathSensitivity.ABSOLUTE) 41 | val frameworkDir: Provider = framework.linkTaskProvider.flatMap { 42 | project.layout.dir(it.outputFile) 43 | } 44 | 45 | @get:OutputDirectory 46 | val outputDirectory: Provider = framework.symbolGraphDir.map { it.dir(language) } 47 | 48 | @TaskAction 49 | open fun extract() { 50 | outputDirectory.get().asFile.apply { 51 | deleteRecursively() 52 | mkdirs() 53 | } 54 | } 55 | 56 | @Internal 57 | protected fun getSdkPath(): String { 58 | val sdk = when (konanTarget) { 59 | is KonanTarget.IOS_ARM64, is KonanTarget.IOS_ARM32 -> "iphoneos" 60 | is KonanTarget.IOS_SIMULATOR_ARM64, is KonanTarget.IOS_X64 -> "iphonesimulator" 61 | is KonanTarget.WATCHOS_ARM32, is KonanTarget.WATCHOS_ARM64, is KonanTarget.WATCHOS_DEVICE_ARM64 -> "watchos" 62 | is KonanTarget.WATCHOS_X64, is KonanTarget.WATCHOS_SIMULATOR_ARM64, is KonanTarget.WATCHOS_X86 -> "watchsimulator" 63 | is KonanTarget.TVOS_ARM64 -> "appletvos" 64 | is KonanTarget.TVOS_X64, is KonanTarget.TVOS_SIMULATOR_ARM64 -> "appletvsimulator" 65 | is KonanTarget.MACOS_X64, is KonanTarget.MACOS_ARM64 -> "macosx" 66 | else -> error("Unsupported target: ${konanTarget.name}") 67 | } 68 | val sdkPathStream = ByteArrayOutputStream() 69 | execOperations.exec { 70 | it.commandLine("/usr/bin/xcrun", "--sdk", sdk, "--show-sdk-path") 71 | it.standardOutput = sdkPathStream 72 | } 73 | return sdkPathStream.toString().trim() 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/kotlin/com/rickclephas/kmp/docc/tasks/internal/GetKotlinDocCRenderUrlTask.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.docc.tasks.internal 2 | 3 | import org.gradle.api.DefaultTask 4 | import org.gradle.api.Project 5 | import org.gradle.api.file.RegularFileProperty 6 | import org.gradle.api.provider.Provider 7 | import org.gradle.api.provider.ValueSource 8 | import org.gradle.api.provider.ValueSourceParameters 9 | import org.gradle.api.tasks.CacheableTask 10 | import org.gradle.api.tasks.Input 11 | import org.gradle.api.tasks.OutputFile 12 | import org.gradle.api.tasks.TaskAction 13 | import org.gradle.api.tasks.TaskProvider 14 | import java.net.HttpURLConnection 15 | import java.net.URL 16 | import java.time.Instant 17 | import java.time.ZoneId 18 | import java.time.format.DateTimeFormatter 19 | 20 | @CacheableTask 21 | internal abstract class GetKotlinDocCRenderUrlTask: DefaultTask() { 22 | 23 | companion object { 24 | val Project.getKotlinDoccRenderUrlTask: TaskProvider 25 | get() = tasks.locateOrRegister("getKotlinDoccRenderUrl", GetKotlinDocCRenderUrlTask::class.java) 26 | } 27 | 28 | abstract class CacheKeyValueSource: ValueSource { 29 | override fun obtain(): String = DateTimeFormatter.ISO_LOCAL_DATE 30 | .withZone(ZoneId.of("UTC")).format(Instant.now()) 31 | } 32 | 33 | @get:Input 34 | @Suppress("unused") 35 | val cacheKey: Provider = project.providers.of(CacheKeyValueSource::class.java) {} 36 | 37 | @get:OutputFile 38 | val outputFile: RegularFileProperty = project.objects.fileProperty() 39 | .convention(project.layout.buildDirectory.file("docc/kotlin-docc-render-url.txt")) 40 | 41 | @TaskAction 42 | fun getUrl() { 43 | val outputFile = outputFile.get().asFile 44 | try { 45 | val releasesUrl = "https://github.com/rickclephas/kotlin-docc-render/releases" 46 | val urlConnection = URL("$releasesUrl/latest").openConnection() as HttpURLConnection 47 | urlConnection.instanceFollowRedirects = false 48 | val version = urlConnection.getHeaderField("Location").split('/').last() 49 | outputFile.writeText("$releasesUrl/download/$version/kotlin-docc-render.zip") 50 | } catch (e: Exception) { 51 | if (outputFile.exists()) { 52 | logger.warn("Failed to get Kotlin-DocC-Render URL, using cache instead.") 53 | } else { 54 | throw IllegalStateException("Failed to get Kotlin-DocC-Render URL", e) 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/kotlin/com/rickclephas/kmp/docc/tasks/internal/Utils.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.docc.tasks.internal 2 | 3 | import org.gradle.api.Task 4 | import org.gradle.api.file.Directory 5 | import org.gradle.api.provider.Provider 6 | import org.gradle.api.tasks.TaskContainer 7 | import org.gradle.api.tasks.TaskProvider 8 | import org.gradle.configurationcache.extensions.capitalized 9 | import org.jetbrains.kotlin.gradle.plugin.mpp.Framework 10 | import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget 11 | 12 | internal val Framework.taskSuffix: String 13 | get() = "${name.capitalized()}${target.taskSuffix}" 14 | 15 | internal val KotlinNativeTarget.taskSuffix: String 16 | get() = targetName.capitalized() 17 | 18 | internal val Framework.baseNameProvider: Provider 19 | get() = project.provider { baseName.replace('-', '_') } 20 | 21 | internal val Framework.symbolGraphDir: Provider 22 | get() = project.layout.buildDirectory.dir("docc/symbol-graphs/${target.targetName}/$name") 23 | 24 | internal val Framework.doccArchiveDir: Provider 25 | get() = baseNameProvider.flatMap { baseName -> 26 | project.layout.buildDirectory.dir("docc/archives/${target.targetName}/$name/$baseName.doccarchive") 27 | } 28 | 29 | internal val KotlinNativeTarget.sourceBundleDir: Provider 30 | get() = project.layout.buildDirectory.dir("docc/source-bundles/${targetName}.docc") 31 | 32 | internal fun TaskContainer.locateOrRegister( 33 | name: String, 34 | type: Class, 35 | vararg constructorArgs: Any 36 | ): TaskProvider { 37 | if (names.contains(name)) return named(name, type) 38 | return register(name, type, *constructorArgs) 39 | } 40 | --------------------------------------------------------------------------------