working with library and example

master
Robinson 2021-08-09 00:27:16 +02:00
commit e1921fd377
46 changed files with 4609 additions and 0 deletions

121
.gitignore vendored Normal file
View File

@ -0,0 +1,121 @@
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff:
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/dictionaries
.idea/**/codeStyles/
.idea/**/codeStyleSettings.xml
# Sensitive or high-churn files:
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.xml
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/shelf/
# Gradle:
.idea/**/gradle.xml
.idea/**/libraries
# CMake
cmake-build-debug/
# Mongo Explorer plugin:
.idea/**/mongoSettings.xml
## File-based project format:
*.iws
## Plugin-specific files:
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
######################
# End JetBrains IDEs #
######################
# From https://github.com/github/gitignore/blob/master/Gradle.gitignore
.gradle
/build/
# Ignore Gradle GUI config
gradle-app.setting
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar
!gradle-wrapper.properties
# Cache of project
.gradletasknamecache
# From https://github.com/github/gitignore/blob/master/Java.gitignore
*.class
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
*.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
##########################################################
# Specific to this module
# iml files are generated by intellij/gradle now
**/*.iml

131
LICENSE Normal file
View File

@ -0,0 +1,131 @@
- GradleVaadin - Gradle Plugin to build Vaadin for use by the VaadinUndertow library
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/GradleVaadin
Copyright 2021
Dorkbox LLC
Extra license information
- Kotlin -
[The Apache Software License, Version 2.0]
https://github.com/JetBrains/kotlin
Copyright 2020
JetBrains s.r.o. and Kotlin Programming Language contributors
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
- ClassGraph - An uber-fast parallelized Java classpath scanner and module scanner
[The Apache Software License, Version 2.0]
https://github.com/classgraph/classgraph
Copyright 2021
Luke Hutchison
- Vaadin - An open platform for building modern web apps for Java back ends
[The Apache Software License, Version 2.0]
https://github.com/vaadin/
Copyright 2021
Vaadin Ltd.
- Executor - Shell, JVM, and SSH command execution on Linux, MacOS, or Windows for Java 8+
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/Executor
Copyright 2021
Dorkbox LLC
Extra license information
- ZT Process Executor -
[The Apache Software License, Version 2.0]
https://github.com/zeroturnaround/zt-exec
Copyright 2014
ZeroTurnaround LLC
- Apache Commons Exec -
[The Apache Software License, Version 2.0]
https://commons.apache.org/proper/commons-exec/
Copyright 2014
The Apache Software Foundation
- Kotlin -
[The Apache Software License, Version 2.0]
https://github.com/JetBrains/kotlin
Copyright 2020
JetBrains s.r.o. and Kotlin Programming Language contributors
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
- Logback - Logback is a logging framework for Java applications
[The Apache Software License, Version 2.0]
http://logback.qos.ch
Copyright 2021
QOS.ch
- SSHJ - SSHv2 library for Java
[The Apache Software License, Version 2.0]
https://github.com/hierynomus/sshj
Copyright 2021
Jeroen van Erp
SSHJ Contributors
Extra license information
- Apache MINA -
[The Apache Software License, Version 2.0]
https://mina.apache.org/sshd-project/
The Apache Software Foundation
- Apache Commons-Net -
[The Apache Software License, Version 2.0]
https://commons.apache.org/proper/commons-net/
The Apache Software Foundation
- JZlib -
[The Apache Software License, Version 2.0]
http://www.jcraft.com/jzlib
Atsuhiko Yamanaka
JCraft, Inc.
- Bouncy Castle Crypto -
[The Apache Software License, Version 2.0]
http://www.bouncycastle.org
The Legion of the Bouncy Castle Inc
- ed25519-java -
[Public Domain, per Creative Commons CC0]
https://github.com/str4d/ed25519-java
https://github.com/str4d
- kotlinx.coroutines - Library support for Kotlin coroutines with multiplatform support
[The Apache Software License, Version 2.0]
https://github.com/Kotlin/kotlinx.coroutines
Copyright 2021
JetBrains s.r.o.
- SLF4J - Simple facade or abstraction for various logging frameworks
[MIT License]
http://www.slf4j.org
Copyright 2021
QOS.ch
- Updates - Software Update Management
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/Updates
Copyright 2021
Dorkbox LLC
Extra license information
- Kotlin -
[The Apache Software License, Version 2.0]
https://github.com/JetBrains/kotlin
Copyright 2020
JetBrains s.r.o. and Kotlin Programming Language contributors
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
- Version - Java Semantic Versioning with exceptions. Minor/Patch number optional and build-after-final-dot (minor/patch) permitted.
[MIT License]
https://git.dorkbox.com/dorkbox/Version
Copyright 2020
Dorkbox LLC
G. Richard Bellamy
Kenduck
Larry Bordowitz <lbordowitz@yahoo-inc.com>
Martin Rüegg <martin.rueegg@bristolpound.org> <martin.rueegg@metaworx.ch>
Zafar Khaja <zafarkhaja@gmail.com>

218
LICENSE.Apachev2 Normal file
View File

@ -0,0 +1,218 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

121
LICENSE.CC0 Normal file
View File

@ -0,0 +1,121 @@
Creative Commons Legal Code
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.

21
LICENSE.JSON Normal file
View File

@ -0,0 +1,21 @@
The JSON License
Copyright (c) 2002 JSON.org
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions: The above copyright notice and this
permission notice shall be included in all copies or substantial portions of the
Software.
The Software shall be used for Good, not Evil.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

21
LICENSE.MIT Normal file
View File

@ -0,0 +1,21 @@
MIT License
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

132
build.gradle.kts Normal file
View File

@ -0,0 +1,132 @@
/*
* Copyright 2020 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.time.Instant
gradle.startParameter.showStacktrace = ShowStacktrace.ALWAYS_FULL // always show the stacktrace!
plugins {
java
`java-gradle-plugin`
`kotlin-dsl`
id("com.gradle.plugin-publish") version "0.14.0"
id("com.dorkbox.Licensing") version "2.9.1"
id("com.dorkbox.VersionUpdate") version "2.4"
id("com.dorkbox.GradleUtils") version "2.9"
kotlin("jvm") version "1.4.31"
}
object Extras {
// set for the project
const val description = "Gradle Plugin to build Vaadin for use by the VaadinUndertow library"
const val group = "com.dorkbox"
const val version = "0.1"
// set as project.ext
const val name = "Gradle Vaadin"
const val id = "GradleVaadin"
const val vendor = "Dorkbox LLC"
const val url = "https://git.dorkbox.com/dorkbox/GradleVaadin"
val tags = listOf("vaadin", "undertow")
val buildDate = Instant.now().toString()
const val coroutineVer = "1.4.3"
const val vaadinVer = "14.1.17"
const val undertowVer = "2.2.9.Final"
}
///////////////////////////////
///// assign 'Extras'
///////////////////////////////
GradleUtils.load("$projectDir/../../gradle.properties", Extras)
GradleUtils.defaults()
GradleUtils.compileConfiguration(JavaVersion.VERSION_1_8)
licensing {
license(License.APACHE_2) {
description(Extras.description)
author(Extras.vendor)
url(Extras.url)
}
}
dependencies {
// the kotlin version is taken from the plugin, so it is not necessary to set it here
compileOnly("org.jetbrains.kotlin:kotlin-gradle-plugin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
// setup node.js
// https://github.com/node-gradle/gradle-node-plugin
// implementation("com.github.node-gradle:gradle-node-plugin:3.1.0")
// Uber-fast, ultra-lightweight Java classpath and module path scanner
implementation("io.github.classgraph:classgraph:4.8.110")
// for parsing JSON
// implementation("org.json:json:20210307")
// implementation("com.vaadin:flow-server:2.1.5")
implementation("com.vaadin:vaadin:${Extras.vaadinVer}")
implementation("com.dorkbox:Executor:3.3")
implementation("com.dorkbox:Version:2.4")
}
tasks.jar.get().apply {
manifest {
// https://docs.oracle.com/javase/tutorial/deployment/jar/packageman.html
attributes["Name"] = Extras.name
attributes["Specification-Title"] = Extras.name
attributes["Specification-Version"] = Extras.version
attributes["Specification-Vendor"] = Extras.vendor
attributes["Implementation-Title"] = "${Extras.group}.${Extras.id}"
attributes["Implementation-Version"] = Extras.buildDate
attributes["Implementation-Vendor"] = Extras.vendor
}
}
/////////////////////////////////
//////// Plugin Publishing + Release
/////////////////////////////////
gradlePlugin {
plugins {
create("GradlePublish") {
id = "${Extras.group}.${Extras.id}"
implementationClass = "dorkbox.gradleVaadin.Vaadin"
}
}
}
pluginBundle {
website = Extras.url
vcsUrl = Extras.url
(plugins) {
"GradlePublish" {
id = "${Extras.group}.${Extras.id}"
displayName = Extras.name
description = Extras.description
tags = Extras.tags
version = Extras.version
}
}
}

1
gradle.properties Normal file
View File

@ -0,0 +1 @@
org.gradle.warning.mode=all

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

185
gradlew vendored Normal file
View File

@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

89
gradlew.bat vendored Normal file
View File

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

15
settings.gradle.kts Normal file
View File

@ -0,0 +1,15 @@
/*
* Copyright 2018 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

View File

@ -0,0 +1,116 @@
package dorkbox.gradleVaadin
import java.io.File
import java.net.URL
class CustomClassFinder(classPath: List<File>, projectDependencies: List<File>) : com.vaadin.flow.server.frontend.scanner.ClassFinder {
// Find everything annotated with the following
// Route.class, NpmPackage.class, NpmPackage.Container.class, WebComponentExporter.class, UIInitListener.class, VaadinServiceInitListener.class
// scans compiled files (NOT SOURCE FILES!)
// make sure to close this!
private val classPathScanResult = io.github.classgraph.ClassGraph()
.overrideClasspath(classPath)
.enableSystemJarsAndModules()
.enableInterClassDependencies()
.enableExternalClasses()
.enableAllInfo()
.scan()
private val dependencyScanResult = io.github.classgraph.ClassGraph()
.overrideClasspath(projectDependencies)
.enableSystemJarsAndModules()
.enableInterClassDependencies()
.enableExternalClasses()
.enableAllInfo()
.scan()
override fun <T : Any?> loadClass(name: String): Class<T>? {
// println("load class: $name")
var clazz: Class<T>? = loadClass(name, classPathScanResult)
if (clazz == null) {
clazz = loadClass(name, dependencyScanResult)
}
return clazz
}
private fun <T : Any?> loadClass(name: String, scanResult: io.github.classgraph.ScanResult): Class<T>? {
@Suppress("UNCHECKED_CAST")
return scanResult.loadClass(name, true) as Class<T>?
}
override fun <T : Any?> getSubTypesOf(type: Class<T>): MutableSet<Class<out T>> {
// println("get subtype of: $type")
val classes = mutableSetOf<Class<out T>>()
getSubTypesOf(type, classPathScanResult, classes)
return classes
}
fun <T : Any?> getSubTypesOf(type: Class<T>, scanResult: io.github.classgraph.ScanResult, classes: MutableSet<Class<out T>>) {
// only those in direct relationship, not subclasses of THOSE subclasses
scanResult.getSubclasses(type.canonicalName).forEach {
// load this class into the current classloader
@Suppress("UNCHECKED_CAST")
classes.add(it.loadClass() as Class<out T>)
// we have to load all of the class dependencies for this class into the current classloader
it.classDependencies.forEach { dep ->
dep.loadClass()
}
}
}
override fun getResource(name: String): URL? {
// println("Get resource $name")
val results = getResource(name, classPathScanResult)
return if (results.isEmpty()) {
Thread.currentThread().contextClassLoader.getResource(name)
} else {
results.firstOrNull()?.url
}
}
private fun getResource(name: String, scanResult: io.github.classgraph.ScanResult): io.github.classgraph.ResourceList {
var results = scanResult.getResourcesWithPath(name)
if (results == null || results.isEmpty()) {
results = scanResult.getResourcesWithLeafName(name)
}
return results
}
override fun getAnnotatedClasses(clazz: Class<out Annotation>): MutableSet<Class<*>> {
// println("getting classes for annotation: '$clazz'")
val classes = mutableSetOf<Class<*>>()
getAnnotatedClasses(clazz, classPathScanResult, classes)
return classes
}
private fun getAnnotatedClasses(clazz: Class<out Annotation>, scanResult: io.github.classgraph.ScanResult, classes: MutableSet<Class<*>>) {
scanResult.getClassesWithAnnotation(clazz.name).forEach {
// load this class into the current classloader
classes.add(it.loadClass())
// we have to load all of the class dependencies for this class into the current classloader
it.classDependencies.forEach { dep ->
dep.loadClass()
}
}
}
/**
* Call this when done!
*/
fun finish() {
classPathScanResult.close()
dependencyScanResult.close()
}
}

View File

@ -0,0 +1,5 @@
package dorkbox.gradleVaadin
import java.io.File
data class DependencyInfo(val group: String, val name: String, val version: String, val file: File)

View File

@ -0,0 +1,65 @@
package dorkbox.gradleVaadin
import org.gradle.api.Project
import org.gradle.process.ExecResult
import java.io.ByteArrayOutputStream
import java.io.File
class Exec(private val proj: Project) {
lateinit var path: String
lateinit var executable: String
lateinit var workingDir: File
lateinit var arguments: List<String>
var environment = System.getenv().toMutableMap()
var ignoreExitValue: Boolean = true
var suppressOutput: Boolean = true
var debug: Boolean = false
fun execute(): ExecResult {
val self = this
return proj.exec {
val exec = this
exec.executable = self.executable
exec.args = self.arguments
// Take care of Windows environments that may contain "Path" OR "PATH" - both existing
// possibly (but not in parallel as of now)
if (self.environment["Path"] != null) {
self.environment["Path"] = self.path + File.pathSeparator + self.environment["Path"]
} else {
self.environment["PATH"] = self.path + File.pathSeparator + self.environment["PATH"]
}
@Suppress("UNCHECKED_CAST")
exec.environment = self.environment as MutableMap<String, Any>
exec.isIgnoreExitValue = self.ignoreExitValue
exec.workingDir = self.workingDir
if (!exec.workingDir.exists()) {
exec.workingDir.mkdirs()
}
if (suppressOutput) {
exec.standardOutput = ByteArrayOutputStream()
exec.errorOutput = ByteArrayOutputStream()
}
if (debug) {
println("\t\tExec: ${exec.executable}")
println("\t\tWorking Dir: ${exec.workingDir}")
println("\t\tSuppressing output: $suppressOutput")
println("\t\tArguments:")
exec.args.forEach {
println("\t\t\t$it")
}
}
}
}
}

View File

@ -0,0 +1,105 @@
package dorkbox.gradleVaadin
import dorkbox.gradleVaadin.node.npm.proxy.ProxySettings
import org.gradle.api.Project
import org.gradle.kotlin.dsl.create
import org.gradle.kotlin.dsl.getByType
import org.gradle.kotlin.dsl.property
open class NodeExtension(project: Project) {
private val buildDir = project.layout.buildDirectory
/**
* The directory where Node.js is unpacked (when download is true)
*/
val workDir = project.objects.directoryProperty().convention(buildDir.dir("nodejs"))
/**
* The directory where npm is installed (when a specific version is defined)
*/
val npmWorkDir = project.objects.directoryProperty().convention(buildDir.dir("npm"))
/**
* The directory where yarn is installed (when a Yarn task is used)
*/
val yarnWorkDir = project.objects.directoryProperty().convention(buildDir.dir("yarn"))
/**
* The Node.js project directory location
* This is where the package.json file and node_modules directory are located
* By default it is at the root of the current project
*/
val nodeProjectDir = project.objects.directoryProperty().convention(buildDir)
/**
* Version of node to download and install (only used if download is true)
* It will be unpacked in the workDir
*/
val version = project.objects.property<String>().convention(DEFAULT_NODE_VERSION)
/**
* Version of npm to use
* If specified, installs it in the npmWorkDir
* If empty, the plugin will use the npm command bundled with Node.js
*/
val npmVersion = project.objects.property<String>().convention("")
/**
* Version of Yarn to use
* Any Yarn task first installs Yarn in the yarnWorkDir
* It uses the specified version if defined and the latest version otherwise (by default)
*/
val yarnVersion = project.objects.property<String>().convention("")
/**
* Base URL for fetching node distributions
* Only used if download is true
* Change it if you want to use a mirror
* Or set to null if you want to add the repository on your own.
*/
val distBaseUrl = project.objects.property<String>().convention("https://nodejs.org/dist")
val npmCommand = project.objects.property<String>().convention("npm")
val npxCommand = project.objects.property<String>().convention("npx")
val yarnCommand = project.objects.property<String>().convention("yarn")
/**
* The npm command executed by the npmInstall task
* By default it is install but it can be changed to ci
*/
val npmInstallCommand = project.objects.property<String>().convention("install")
/**
* Whether to download and install a specific Node.js version or not
* If false, it will use the globally installed Node.js
* If true, it will download node using above parameters
* Note that npm is bundled with Node.js
*/
val download = project.objects.property<Boolean>().convention(true)
/**
* Whether the plugin automatically should add the proxy configuration to npm and yarn commands
* according the proxy configuration defined for Gradle
*
* Disable this option if you want to configure the proxy for npm or yarn on your own
* (in the .npmrc file for instance)
*
*/
val nodeProxySettings = project.objects.property<ProxySettings>().convention(ProxySettings.SMART)
companion object {
const val NAME = "node"
const val DEFAULT_NODE_VERSION = "14.15.4"
const val DEFAULT_NPM_VERSION = "6.14.10"
@JvmStatic
operator fun get(project: Project): NodeExtension {
return project.extensions.getByType()
}
@JvmStatic
fun create(project: Project): NodeExtension {
return project.extensions.create(NAME, project)
}
}
}

View File

@ -0,0 +1,111 @@
/*
* Copyright 2021 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.gradleVaadin
import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.tasks.*
import org.gradle.api.tasks.wrapper.Wrapper
import org.gradle.util.GradleVersion
import java.io.File
import java.net.URL
open class
PrepJarsTask : DefaultTask() {
companion object {
}
@OutputFiles
val allLibrariesRev = mutableMapOf<File, String>()
init {
group = "vaadin"
description = "Prepares and checks the libraries used by all projects."
outputs.cacheIf { false }
outputs.upToDateWhen { false }
}
@TaskAction
fun run() {
val librariesByFileName = mutableMapOf<String, File>()
synchronized(allLibrariesRev) {
// make sure all projects and subprojects are considered
project.allprojects.forEach { subProject ->
val resolveAllDependencies = Vaadin.resolveAllDependencies(subProject).flatMap { it.artifacts }
resolveAllDependencies.forEach { artifact ->
val file = artifact.file
var fileName = file.name
var firstNumCheck = 0
while (librariesByFileName.containsKey(fileName)) {
// whoops! this is not good! Rename the file so it will be included. THIS PROBLEM ACTUALLY EXISTS, and is by accident!
// if the target FILE is the same file (as the filename) then it's OK for this to be a duplicate
if (file != librariesByFileName[fileName]) {
fileName = "${file.nameWithoutExtension}_DUP_${firstNumCheck++}.${file.extension}"
} else {
// the file name and path are the same, meaning this is just a duplicate library
// instead of a DIFFERENT library with the same library file name.
break
}
}
if (firstNumCheck != 0) {
println("\tTarget file exists already! Renaming to $fileName")
}
// println("adding: " + file)
librariesByFileName[fileName] = file
allLibrariesRev[file] = fileName
}
}
}
}
// get all jars needed on the library classpath, for RUNTIME (this is placed in the jar manifest)
// NOTE: This must be referenced via a TASK, otherwise it will not work.
fun getJarLibraryClasspath(project: Project): String {
val libraries = mutableMapOf<String, File>()
val resolveAllDependencies = Vaadin.resolveRuntimeDependencies(project).dependencies
synchronized(allLibrariesRev) { // we must synchronize on it for thread safety
resolveAllDependencies.forEach { dep ->
dep.artifacts.forEach { artifact ->
val file = artifact.file
// get the file info from the reverse lookup, because we might have mangled the filename!
val cacheFileName = allLibrariesRev[file]!!
libraries[cacheFileName] = file
}
}
}
return libraries.keys.sorted().joinToString(prefix = "lib/", separator = " lib/", postfix = "\r\n")
}
fun copyLibrariesTo(targetDir: File) {
synchronized(allLibrariesRev) {
allLibrariesRev.forEach { (file, fileName) ->
// we don't overwrite the file if it already exists
val destFile = File(targetDir, fileName)
if (!destFile.exists()) {
file.copyTo(destFile)
}
}
}
}
}

View File

@ -0,0 +1,361 @@
package dorkbox.gradleVaadin
import com.vaadin.flow.server.Constants
import com.vaadin.flow.server.frontend.FrontendUtils
import elemental.json.impl.JsonUtil
import org.jetbrains.kotlin.gradle.internal.ensureParentDirsCreated
import java.io.File
import java.io.FileOutputStream
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
class Updater {
companion object {
private const val DEPENDENCIES = "dependencies"
private const val DEV_DEPENDENCIES = "devDependencies"
private const val DEP_NAME_KEY = "name"
private const val DEP_NAME_DEFAULT = "no-name"
private const val DEP_NAME_FLOW_DEPS = "@vaadin/flow-deps"
private const val DEP_VERSION_KEY = "version"
private const val DEP_VERSION_DEFAULT = "1.0.0"
private const val DEP_LICENSE_KEY = "license"
private const val DEP_LICENSE_DEFAULT = "UNLICENSED"
private const val APP_PACKAGE_HASH = "vaadinAppPackageHash"
private const val FORCE_INSTALL_HASH = "Main dependencies updated, force install"
private const val VERSION = "version"
private const val SHRINK_WRAP = "@vaadin/vaadin-shrinkwrap"
private val regex = "\\\\".toRegex()
fun createMissingPackageJson(jsonPackageFile: File, generatedJsonPackageFile: File, polymerVersion: String): Boolean {
val mainContent = getOrCreateJson(jsonPackageFile)
// where the custom package.json is relative to our build
// NOTE: this ABSOLUTELY MUST start with a "./", otherwise NPM freaks out
val generatedDirAsRelative = "./" + relativize(jsonPackageFile.parentFile, generatedJsonPackageFile.parentFile)
var modified = updateMainDefaultDependencies(mainContent, polymerVersion, generatedDirAsRelative)
if (modified) {
if (mainContent.hasKey(APP_PACKAGE_HASH)) {
println("\t\t" + FORCE_INSTALL_HASH)
mainContent.put(APP_PACKAGE_HASH, FORCE_INSTALL_HASH)
} else {
mainContent.put(APP_PACKAGE_HASH, "")
}
writeJson(jsonPackageFile, mainContent)
}
var customContent = getJson(generatedJsonPackageFile)
if (customContent == null) {
modified = true
customContent = elemental.json.Json.createObject()
updateAppDefaultDependencies(customContent)
writeJson(generatedJsonPackageFile, customContent)
}
return modified
}
private fun updateMainDefaultDependencies(packageJson: elemental.json.JsonObject, polymerVersion: String, generatedDirAsRelative: String):
Boolean {
var added = 0
added += addDependency(packageJson, null, DEP_NAME_KEY, DEP_NAME_DEFAULT)
added += addDependency(packageJson, null, DEP_LICENSE_KEY, DEP_LICENSE_DEFAULT)
added += addDependency(packageJson, DEPENDENCIES, "@polymer/polymer", polymerVersion)
added += addDependency(packageJson, DEPENDENCIES, "@webcomponents/webcomponentsjs", "^2.2.10")
// dependency for the custom package.json placed in the generated folder.
added += addDependency(packageJson, DEPENDENCIES, DEP_NAME_FLOW_DEPS, generatedDirAsRelative)
added += addDependency(packageJson, DEV_DEPENDENCIES, "webpack", "4.30.0")
added += addDependency(packageJson, DEV_DEPENDENCIES, "webpack-cli", "3.3.10")
added += addDependency(packageJson, DEV_DEPENDENCIES, "webpack-dev-server", "3.9.0")
added += addDependency(packageJson, DEV_DEPENDENCIES, "webpack-babel-multi-target-plugin", "2.3.3")
added += addDependency(packageJson, DEV_DEPENDENCIES, "copy-webpack-plugin", "5.1.0")
added += addDependency(packageJson, DEV_DEPENDENCIES, "compression-webpack-plugin", "3.0.1")
added += addDependency(packageJson, DEV_DEPENDENCIES, "webpack-merge", "4.2.2")
added += addDependency(packageJson, DEV_DEPENDENCIES, "raw-loader", "3.0.0")
if (added > 0) {
println("\t\tAdded $added dependencies to main package.json")
}
return added > 0
}
private fun updateAppDefaultDependencies(packageJson: elemental.json.JsonObject) {
addDependency(packageJson, null, DEP_NAME_KEY, DEP_NAME_FLOW_DEPS)
addDependency(packageJson, null, DEP_VERSION_KEY, DEP_VERSION_DEFAULT)
addDependency(packageJson, null, DEP_LICENSE_KEY, DEP_LICENSE_DEFAULT)
}
fun updateGeneratedPackageJsonDependencies(packageJson: elemental.json.JsonObject,
scannedDependencies: Map<String, String>,
mainPackageJson: File,
generatedPackageJson: File,
npmModulesDir: File,
packageLockFile: File): Pair<Boolean, Boolean> {
println("\t\tChecking '$generatedPackageJson' to see if all dependencies are added....")
var added = 0
// Add application dependencies
for ((key, value) in scannedDependencies) {
added += addDependency(packageJson, DEPENDENCIES, key, value)
}
if (added > 0) {
println("\t\tAdded $added dependencies")
} else {
println("\t\tNo extra dependencies added...")
}
var doCleanUp = false
val dependencies = packageJson.getObject(DEPENDENCIES)
if (dependencies != null) {
// Remove obsolete dependencies
val copyOfKeys = dependencies.keys()
for (key in copyOfKeys) {
if (!scannedDependencies.containsKey(key)) {
dependencies.remove(key)
}
}
val flowDeps = npmModulesDir.resolve(DEP_NAME_FLOW_DEPS).resolve(Constants.PACKAGE_JSON)
doCleanUp = !isReleaseVersion(dependencies, mainPackageJson, generatedPackageJson, flowDeps, packageLockFile)
}
return Pair(added > 0, doCleanUp)
}
fun generateWebPackGeneratedConfig(configFile: File, sourceFrontEndDir: File, webpackOutputDir: File,
flowImportFile: File, relativeStaticResources: String,
customClassFinder: CustomClassFinder) {
val absoluteConfigPath = configFile.parentFile.absoluteFile
val absoluteSourceFrontEndPath = sourceFrontEndDir.absoluteFile
val absoluteWebpackOutputPath = webpackOutputDir.absoluteFile
// Generated file is always re-written
val generatedFile = configFile.parentFile.resolve(FrontendUtils.WEBPACK_GENERATED).absoluteFile
println("\t\tUpdating generated webpack file: $generatedFile")
val resource = customClassFinder.getResource(FrontendUtils.WEBPACK_GENERATED)
resource?.openStream()?.copyTo(FileOutputStream(generatedFile))
val frontEndReplacement = relativize(absoluteConfigPath, absoluteSourceFrontEndPath)
val frontendLine = "const frontendFolder = require('path').resolve(__dirname, '$frontEndReplacement');"
val webPackReplacement = relativize(absoluteConfigPath, absoluteWebpackOutputPath)
val outputLine = "const mavenOutputFolderForFlowBundledFiles = require('path').resolve(__dirname, '$webPackReplacement');"
val flowImportsReplacement = relativize(absoluteConfigPath, flowImportFile)
val mainLine = "const fileNameOfTheFlowGeneratedMainEntryPoint = require('path').resolve(__dirname, '$flowImportsReplacement');"
// NOTE: new stuff. Change 'src/main/webapp' -> 'webapp' (or META-INF? which is where all static resources are served...)
val webappDirLine = "contentBase: [mavenOutputFolderForFlowBundledFiles, '$relativeStaticResources'],"
val lines = generatedFile.readLines(Charsets.UTF_8).toMutableList()
for (i in lines.indices) {
val line = lines[i].trim()
if (line.startsWith("const frontendFolder")) {
lines[i] = frontendLine
} else if (line.startsWith("const mavenOutputFolderForFlowBundledFiles") && line != outputLine) {
lines[i] = outputLine
} else if (line.startsWith("const fileNameOfTheFlowGeneratedMainEntryPoint") && line != mainLine) {
lines[i] = mainLine
} else if (line.startsWith("contentBase: [mavenOutputFolderForFlowBundledFiles") && line != webappDirLine) {
lines[i] = webappDirLine
}
}
generatedFile.writeText(lines.joinToString(separator = System.lineSeparator()), Charsets.UTF_8)
}
/**
* Check and update the main package hash in all cases as we might have updated the main package with new dependencies.
*
* @return true if hash has changed
*/
fun updatePackageHash(jsonPackageFile: File, generatedPackageJson: elemental.json.JsonObject): Boolean {
var content = ""
// If we have dependencies generate hash on ordered content.
if (generatedPackageJson.hasKey(DEPENDENCIES)) {
val dependencies = generatedPackageJson.getObject(DEPENDENCIES)
content = dependencies.keys().map {
String.format("\"%s\": \"%s\"", it, dependencies.getString(it))
}
.sortedDescending()
.joinToString(". \n ")
}
val hash = getHash(content)
val origJson = getJson(jsonPackageFile)!!
val modified = (!origJson.hasKey(APP_PACKAGE_HASH) || hash != origJson.getString(APP_PACKAGE_HASH))
if (modified) {
println("\tChanges to dependencies found! Saving new checksum")
origJson.put(APP_PACKAGE_HASH, hash)
writeJson(jsonPackageFile, origJson)
}
return modified
}
fun getOrCreateJson(jsonFile: File): elemental.json.JsonObject {
return getJson(jsonFile) ?: elemental.json.Json.createObject()
}
fun getJson(jsonFile: File): elemental.json.JsonObject? {
if (jsonFile.canRead()) {
return JsonUtil.parse(jsonFile.readText(Charsets.UTF_8)) as elemental.json.JsonObject?
}
return null
}
fun writeJson(jsonFile: File, jsonObject: elemental.json.JsonObject) {
jsonFile.ensureParentDirsCreated()
jsonFile.writeText(JsonUtil.stringify(jsonObject, 2) + "\n", Charsets.UTF_8)
}
// add, if necessary a dependency. If it's missing, return 1, otherwise 0
private fun addDependency(json: elemental.json.JsonObject, key: String?, pkg: String?, version: String): Int {
@Suppress("NAME_SHADOWING")
var json = json
if (key != null) {
if (!json.hasKey(key)) {
json.put(key, elemental.json.Json.createObject())
}
json = json.get(key)
}
if (!json.hasKey(pkg) || json.getString(pkg) != version) {
json.put(pkg, version)
println("\t\t\tAdded '$pkg':'$version'")
return 1
}
return 0
}
/**
* Compares vaadin-shrinkwrap dependency version from the `dependencies` object with the current vaadin-shrinkwrap version
* (retrieved from various sources like package.json, package-lock.json).
*/
private fun isReleaseVersion(dependencies: elemental.json.JsonObject,
mainPackageJson: File,
generatedPackageJson: File,
flowDeps: File,
packageLockFile: File): Boolean {
var shrinkWrapVersion: String? = null
if (dependencies.hasKey(SHRINK_WRAP)) {
shrinkWrapVersion = dependencies.getString(SHRINK_WRAP)
}
return shrinkWrapVersion == getCurrentShrinkWrapVersion(mainPackageJson, generatedPackageJson, flowDeps, packageLockFile)
}
// get's the shrink-wrap version info, in the following order:
// MAIN json -> GENERATED json -> FLOW-DEPENDENCY json -> MAIN package-lock
private fun getCurrentShrinkWrapVersion(mainPackageJson: File,
generatedPackageJson: File,
flowDeps: File,
packageLockFile: File): String? {
var shrinkWrapVersion = getShrinkWrapVersion(mainPackageJson)
if (shrinkWrapVersion != null) {
return shrinkWrapVersion
}
shrinkWrapVersion = getShrinkWrapVersion(generatedPackageJson)
if (shrinkWrapVersion != null) {
return shrinkWrapVersion
}
shrinkWrapVersion = getShrinkWrapVersion(flowDeps)
if (shrinkWrapVersion != null) {
return shrinkWrapVersion
}
shrinkWrapVersion = getShrinkWrapVersion(packageLockFile)
return shrinkWrapVersion
}
private fun getShrinkWrapVersion(packageFile: File): String? {
val packageJson = getJson(packageFile) ?: return null
if (!packageJson.hasKey(DEPENDENCIES)) {
return null
}
val dependencies = packageJson.getObject(DEPENDENCIES)
if (!dependencies.hasKey(SHRINK_WRAP)) {
return null
}
if (dependencies.hasKey(SHRINK_WRAP)) {
if (packageFile.nameWithoutExtension.contains("-lock")) {
// the package.lock.json file has this!
val shrinkWrap = dependencies.getObject(SHRINK_WRAP)
if (shrinkWrap.hasKey(VERSION)) {
return shrinkWrap.get<elemental.json.JsonValue>(VERSION).asString()
}
} else {
return dependencies.getString(SHRINK_WRAP)
}
}
return null
}
fun relativize(sourceFile: File, targetFile: File): String {
// the regex is to make sure that the '/' is properly escaped in the file (otherwise it's interpreted incorrectly by webpack)
return targetFile.toRelativeString(sourceFile).replace(regex, "/")
}
fun compareAndCopy(fileSource: File, fileTarget: File) {
val relative = relativize(fileSource, fileTarget)
if (getHash(fileSource) != getHash(fileTarget)) {
val canRead = fileSource.copyTo(fileTarget, true).canRead()
if (canRead) {
println("\t\tCopy SUCCESS: $fileSource -> $relative")
} else {
println("\t\tCopy FAILED: $fileSource -> $relative")
}
}
else {
println("\t\tCopy SKIP: $fileSource -> $relative")
}
}
private fun getHash(file: File): String {
return if (file.canRead()) {
getHash(file.readText(Charsets.UTF_8))
} else {
"cannot read: ${file.absolutePath}"
}
}
private fun getHash(content: String): String {
return if (content.isEmpty()) {
content
} else try {
val digest: MessageDigest = MessageDigest.getInstance("SHA-256")
digest.digest(content.toByteArray()).joinToString("", transform = { "%02x".format(it) })
} catch (e: NoSuchAlgorithmException) {
// Unrecoverable runtime exception, it should not happen
throw RuntimeException("Unable to find a provider for SHA-256 algorithm", e)
}
}
}
}

View File

@ -0,0 +1,794 @@
/*
* Copyright 2020 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.gradleVaadin
import dorkbox.gradleVaadin.node.npm.proxy.ProxySettings
import dorkbox.gradleVaadin.node.npm.task.NpmInstallTask
import dorkbox.gradleVaadin.node.npm.task.NpmSetupTask
import dorkbox.gradleVaadin.node.npm.task.NpmTask
import dorkbox.gradleVaadin.node.npm.task.NpxTask
import dorkbox.gradleVaadin.node.task.NodeSetupTask
import dorkbox.gradleVaadin.node.task.NodeTask
import dorkbox.gradleVaadin.node.variant.VariantComputer
import dorkbox.gradleVaadin.node.yarn.task.YarnInstallTask
import dorkbox.gradleVaadin.node.yarn.task.YarnSetupTask
import dorkbox.gradleVaadin.node.yarn.task.YarnTask
import com.vaadin.flow.server.Constants
import com.vaadin.flow.server.frontend.FrontendUtils
import com.vaadin.flow.server.frontend.scanner.FrontendDependenciesScanner
import dorkbox.gradleVaadin.node.deps.DependencyScanner
import elemental.json.Json
import elemental.json.impl.JsonUtil
import org.gradle.api.GradleException
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.tasks.SourceSetContainer
import org.gradle.internal.impldep.org.bouncycastle.asn1.x500.style.RFC4519Style
import org.gradle.kotlin.dsl.named
import org.gradle.kotlin.dsl.register
import org.jetbrains.kotlin.gradle.internal.ensureParentDirsCreated
import org.jetbrains.kotlin.gradle.targets.js.npm.packageJson
import java.io.File
/**
* For managing Vaadin gradle tasks
*/
@Suppress("UnstableApiUsage", "unused")
class Vaadin : Plugin<Project> {
private lateinit var project: Project
private lateinit var config: VaadinConfig
override fun apply(project: Project) {
this.project = project
// https://discuss.gradle.org/t/can-a-plugin-itself-add-buildscript-dependencies-and-then-apply-a-plugin/25039/4
apply("java")
// apply("kotlin-dsl")
// apply("com.vaadin:flow-server")
// apply("com.github.node-gradle.node")
// project.plugins.apply(NodePlugin::class.java)
// Create the Plugin extension object (for users to configure publishing).
config = project.extensions.create("vaadin", VaadinConfig::class.java, project)
// have to create the task
project.tasks.create("prepare_jar_libraries", PrepJarsTask::class.java)
project.repositories.apply {
maven { setUrl("https://maven.vaadin.com/vaadin-addons") } // Vaadin Addons
maven { setUrl("https://maven.vaadin.com/vaadin-prereleases") } // Pre-releases
maven { setUrl("https://oss.sonatype.org/content/repositories/vaadin-snapshots") } // Vaadin Snapshots
}
project.dependencies.apply {
add("implementation", "com.vaadin:vaadin:${config.vaadinVersion}")
add("implementation", "com.dorkbox:VaadinUndertow:0.1")
}
// NOTE: NPM will ALWAYS install packages to the "node_modules" directory that is a sibiling to the packages.json directory!
val nodeExtension = NodeExtension.create(project)
addGlobalTypes()
addTasks()
addNpmRule()
addYarnRule()
project.afterEvaluate {
if (nodeExtension.download.get()) {
nodeExtension.distBaseUrl.orNull?.let { addRepository(it) }
configureNodeSetupTask(nodeExtension)
}
}
// val npmInstallTask = project.tasks.named(NpmInstallTask.NAME).get().apply {
// this as NpmInstallTask
// packageJson()
//
// }
val nodeSetup = project.tasks.named(NodeSetupTask.NAME).apply {
}
project.tasks.create("compileResources-DEV").apply {
dependsOn(nodeSetup, project.tasks.named("classes"))
mustRunAfter(nodeSetup)
group = "vaadin"
description = "Compile Vaadin resources for Development"
// enable caching for the compile task
outputs.cacheIf { true }
inputs.files(
"${project.projectDir}/package.json",
"${project.projectDir}/package-lock.json",
"${project.projectDir}/webpack.config.js",
"${project.projectDir}/webpack.production.js"
)
outputs.dir("${project.buildDir}/config")
outputs.dir("${project.buildDir}/resources")
outputs.dir("${project.buildDir}/nodejs")
outputs.dir("${project.buildDir}/node_modules")
doLast {
compileVaadinResources(project, false)
}
}
project.tasks.create("compileResources-PROD").apply {
dependsOn(nodeSetup, project.tasks.named("classes"))
mustRunAfter(nodeSetup)
group = "vaadin"
description = "Compile Vaadin resources for Production"
// enable caching for the compile task
outputs.cacheIf { true }
inputs.files(
"${project.projectDir}/package.json",
"${project.projectDir}/package-lock.json",
"${project.projectDir}/webpack.config.js",
"${project.projectDir}/webpack.production.js"
)
outputs.dir("${project.buildDir}/resources/META-INF/resources/VAADIN")
outputs.dir("${project.buildDir}/node_modules")
doLast {
println("work? $didWork")
compileVaadinResources(project, true)
}
}
// have to get the configuration extension data
// required to make sure the tasks run in the correct order
project.afterEvaluate {
// config.nodeFixer = FixNodeInstall(project, config)
// // fix the maven source jar
// val sourceJarTask = project.tasks.findByName("sourceJar") as Jar
// sourceJarTask.apply {
// val sourceSets = project.extensions.getByName("sourceSets") as org.gradle.api.tasks.SourceSetContainer
// val mainSourceSet: SourceSet = sourceSets.getByName("main")
//
// // want to included java + kotlin for the sources
//
// // kotlin stuff. Sometimes kotlin depends on java files, so the kotlin sourcesets have BOTH java + kotlin.
// // we want to make sure to NOT have both, as it will screw up creating the jar!
// try {
// val kotlin = (mainSourceSet as org.gradle.api.internal.HasConvention)
// .convention
// .getPlugin(KotlinSourceSet::class.java)
// .kotlin
//
// val srcDirs = kotlin.srcDirs
// val kotlinFiles = kotlin.asFileTree.matching { it: PatternFilterable ->
// // find out if this file (usually, just a java file) is ALSO in the java sourceset.
// // this is to prevent DUPLICATES in the jar, because sometimes kotlin must be .kt + .java in order to compile!
// val javaFiles = mainSourceSet.java.files.map { file ->
// // by definition, it MUST be one of these
// val base = srcDirs.first {
// // find out WHICH src dir base path it is
// val path = project.buildDir.relativeTo(it)
// path.path.isNotEmpty()
// }
// file.relativeTo(base).path
// }
//
// it.setExcludes(javaFiles)
// }
//
// from(kotlinFiles)
// } catch (ignored: Exception) {
// // maybe we don't have kotlin for the project
// }
//
// // java stuff (it is compiled AFTER kotlin), and it is ALREADY included!
// // kotlin is always compiled first
// // from(mainSourceSet.java)
// }
// // output how much the time-outs are
// val durationString = config.httpTimeout.toString().substring(2)
// .replace("(\\d[HMS])(?!$)", "$1 ").toLowerCase()
//
//
// val fullReleaseTimeout = Duration.ofMillis(config.retryDelay.toMillis() * config.retryLimit)
// val fullReleaseString = fullReleaseTimeout.toString().substring(2)
// .replace("(\\d[HMS])(?!$)", "$1 ").toLowerCase()
//
// project.tasks.findByName("publishToSonatype")?.doFirst {
// println("\tPublishing to Sonatype: ${config.groupId}:${config.artifactId}:${config.version}")
// println("\t\tSonatype HTTP timeout: $durationString")
// println("\t\tSonatype API timeout: $fullReleaseString")
// }
}
project.childProjects.values.forEach {
it.pluginManager.apply(Vaadin::class.java)
}
}
// required to make sure the plugins are correctly applied. ONLY applying it to the project WILL NOT work.
// The plugin must also be applied to the root project
private fun apply(id: String) {
if (project.rootProject.pluginManager.findPlugin(id) == null) {
project.rootProject.pluginManager.apply(id)
}
if (project.pluginManager.findPlugin(id) == null) {
project.pluginManager.apply(id)
}
}
private fun addGlobalTypes() {
addGlobalType<NodeTask>()
addGlobalType<NpmTask>()
addGlobalType<NpxTask>()
addGlobalType<YarnTask>()
addGlobalType<ProxySettings>()
}
private inline fun <reified T> addGlobalType() {
project.extensions.extraProperties[T::class.java.simpleName] = T::class.java
}
private fun addTasks() {
project.tasks.register<NpmInstallTask>(NpmInstallTask.NAME)
project.tasks.register<YarnInstallTask>(YarnInstallTask.NAME)
project.tasks.register<NodeSetupTask>(NodeSetupTask.NAME)
project.tasks.register<NpmSetupTask>(NpmSetupTask.NAME)
project.tasks.register<YarnSetupTask>(YarnSetupTask.NAME)
}
private fun addNpmRule() { // note this rule also makes it possible to specify e.g. "dependsOn npm_install"
project.tasks.addRule("Pattern: \"npm_<command>\": Executes an NPM command.") {
val taskName = this
if (taskName.startsWith("npm_")) {
project.tasks.create(taskName, NpmTask::class.java) {
val tokens = taskName.split("_").drop(1) // all except first
npmCommand.set(tokens)
if (tokens.first().equals("run", ignoreCase = true)) {
dependsOn(NpmInstallTask.NAME)
}
}
}
}
}
private fun addYarnRule() { // note this rule also makes it possible to specify e.g. "dependsOn yarn_install"
project.tasks.addRule("Pattern: \"yarn_<command>\": Executes an Yarn command.") {
val taskName = this
if (taskName.startsWith("yarn_")) {
project.tasks.create(taskName, YarnTask::class.java) {
val tokens = taskName.split("_").drop(1) // all except first
yarnCommand.set(tokens)
if (tokens.first().equals("run", ignoreCase = true)) {
dependsOn(YarnInstallTask.NAME)
}
}
}
}
}
private fun addRepository(distUrl: String) {
project.repositories.ivy {
name = "Node.js"
setUrl(distUrl)
patternLayout {
artifact("v[revision]/[artifact](-v[revision]-[classifier]).[ext]")
}
metadataSources {
artifact()
}
content {
includeModule("org.nodejs", "node")
}
}
}
private fun configureNodeSetupTask(nodeExtension: NodeExtension) {
val variantComputer = VariantComputer()
val nodeArchiveDependencyProvider = variantComputer.computeNodeArchiveDependency(nodeExtension)
val archiveFileProvider = nodeArchiveDependencyProvider.map { nodeArchiveDependency ->
resolveNodeArchiveFile(nodeArchiveDependency)
}
project.tasks.named<NodeSetupTask>(NodeSetupTask.NAME) {
nodeArchiveFile.set(project.layout.file(archiveFileProvider))
}
}
private fun resolveNodeArchiveFile(name: String): File {
val dependency = project.dependencies.create(name)
val configuration = project.configurations.detachedConfiguration(dependency)
configuration.isTransitive = false
return configuration.resolve().single()
}
companion object {
const val NODE_GROUP = "Node"
const val NPM_GROUP = "npm"
const val YARN_GROUP = "Yarn"
// For more info, see:
// https://github.com/vaadin/flow/tree/master/flow-maven-plugin
// https://vaadin.com/docs/v14/flow/production/tutorial-production-mode-advanced.html
fun compileVaadinResources(project: Project, productionMode: Boolean) {
val variantComputer = VariantComputer()
val config = project.extensions.getByName("vaadin") as VaadinConfig
val nodeExtension = NodeExtension[project]
val nodeDirProvider = variantComputer.computeNodeDir(nodeExtension)
// val npmDirProvider = variantComputer.computeNpmDir(nodeExtension, nodeDirProvider)
val nodeDir = nodeDirProvider.get().asFile
val nodeModulesDir = variantComputer.computeNodeModulesDir(nodeExtension).get().asFile
// SEE: com.vaadin.flow.server.startup.DevModeInitializer
val baseDir = project.rootDir
val buildDir = baseDir.resolve("build")
val frontendDir = baseDir.resolve(FrontendUtils.FRONTEND)
val webAppDir = baseDir.resolve("resources")
val metaInfDir = webAppDir.resolve("META-INF")
val vaadinDir = metaInfDir.resolve("resources").resolve("VAADIN")
val generatedDir = buildDir.resolve(FrontendUtils.FRONTEND)
// This file also points to the generated package file in the generated frontend dir
val origWebPackFile = baseDir.resolve(FrontendUtils.WEBPACK_CONFIG)
val origWebPackProdFile = baseDir.resolve("webpack.production.js")
val jsonPackageFile = buildDir.resolve(Constants.PACKAGE_JSON)
val jsonPackageLockFile = buildDir.resolve("package-lock.json")
val webPackFile = buildDir.resolve(FrontendUtils.WEBPACK_CONFIG)
val webPackProdFile = buildDir.resolve("webpack.production.js")
val webPackExecutableFile = nodeModulesDir.resolve("webpack").resolve("bin").resolve("webpack.js").absoluteFile
val generatedJsonPackageFile = generatedDir.resolve(Constants.PACKAGE_JSON)
val tokenFile: File
val generatedFilesDir: File
// setup the token file
run {
// This matches the AppLauncher!
val prodTokenFile = vaadinDir.resolve(FrontendUtils.TOKEN_FILE)
val devTokenFile = buildDir.resolve(FrontendUtils.TOKEN_FILE)
// always delete BOTH token files! We read these files in the launcher to determine if we are in DEV or PROD mode!
prodTokenFile.delete()
devTokenFile.delete()
val prodGeneratedFilesDir = vaadinDir.resolve(FrontendUtils.FRONTEND).absoluteFile
val devGeneratedFilesDir = buildDir.resolve(FrontendUtils.FRONTEND).absoluteFile
if (productionMode) {
tokenFile = prodTokenFile
generatedFilesDir = prodGeneratedFilesDir
devGeneratedFilesDir.deleteRecursively() // always delete the OTHER generated directory
} else {
tokenFile = devTokenFile
generatedFilesDir = devGeneratedFilesDir
prodGeneratedFilesDir.deleteRecursively() // always delete the OTHER generated directory
}
}
// REGARDING the current version of polymer.
// see: com.vaadin.flow.server.frontend.NodeUpdater.updateMainDefaultDependencies
val polymerVersion = "3.2.0"
println("\tCompiling Vaadin resources")
println("\tProduction mode: $productionMode")
println("\t\tPolymer version: $polymerVersion")
println("\t\tBase Dir: $baseDir")
println("\t\tNode Dir: ${nodeDir}")
println("\t\tGenerated Dir: $generatedDir")
println("\t\tWebPack Executable: $webPackExecutableFile")
// ext.vaadin_charts_license = "00df96cb-e8da-4111-8e72-e3a1fc8b394b" // registered to ajraman@net-ref.com
//ext.vaadin_spreadsheets_license = "bc7e7ea0-3068-471d-ac3f-cbfe7be4d7ec" // registered to ajraman@net-ref.com
// // vaadin charts/spreadsheets licenses
// "-Dvaadin.charts.developer.license=$vaadin_charts_license",
// "-Dvaadin.spreadsheet.developer.license=$vaadin_spreadsheets_license",]
// -Dvaadin.proKey=[pro-key-string]
// The default configuration extends from the runtime configuration, which means that it contains all the dependencies and artifacts of the runtime configuration, and potentially more.
// THIS MUST BE IN "afterEvaluate".
// Using the "runtime" classpath (weirdly) DOES NOT WORK. Only "default" works.
// val projectDependencies = resolve(project.configurations["default"]).map { it.file }
val projectDependencies: List<File> =
resolveRuntimeDependencies(project).dependencies.flatMap { dep -> dep.artifacts.map { artifact -> artifact.file } }
// val projectDependencies = resolve(project.configurations["default"]).map { it.file }
val sourceSets = project.extensions.getByName("sourceSets") as SourceSetContainer
val classPath = sourceSets.getByName("main").output.classesDirs.map { it.absoluteFile } as MutableList<File>
classPath.addAll(projectDependencies)
val customClassFinder = CustomClassFinder(classPath, projectDependencies)
try {
////////////////////////////////
// PREPARE FRONT END
////////////////////////////////
run {
println("\tPreparing frontend...")
// always delete the VAADIN directory!
vaadinDir.deleteRecursively()
// always delete the generated directory
generatedFilesDir.deleteRecursively()
// copy our package.json + package-lock.json + webpack.config.js files to the build dir (only if they are different!)
Updater.compareAndCopy(origWebPackFile, webPackFile)
Updater.compareAndCopy(origWebPackProdFile, webPackProdFile)
if (!generatedDir.exists() && !generatedDir.mkdirs()) {
throw GradleException("Unable to continue. Target generation dir $generatedDir cannot be created")
}
val wasModified = Updater.createMissingPackageJson(jsonPackageFile, generatedJsonPackageFile, polymerVersion)
if (wasModified) {
println("\t\tPackage file was modified!")
}
val flowImportFile = generatedFilesDir.resolve(FrontendUtils.IMPORTS_NAME)
// we have to make additional customizations to the webpack.generated.js file
val relativeStaticResources = Updater.relativize(baseDir, metaInfDir).replace("./", "")
Updater.generateWebPackGeneratedConfig(
webPackFile,
frontendDir,
vaadinDir,
flowImportFile,
relativeStaticResources,
customClassFinder
)
}
////////////////////////////////
// BUILD FRONT END
////////////////////////////////
run {
println("\t\tCreating configuration token file: $tokenFile")
// propagateBuildInfo & updateBuildInfo (combined from maven goals, because the token file is ONLY used for enableImportsUpdate)
val buildInfo = Json.createObject()
buildInfo.put(Constants.SERVLET_PARAMETER_COMPATIBILITY_MODE, false)
buildInfo.put(Constants.SERVLET_PARAMETER_PRODUCTION_MODE, productionMode)
buildInfo.put("polymer.version", polymerVersion)
// used for defining folder paths for dev server
if (!productionMode) {
buildInfo.put(Constants.NPM_TOKEN, buildDir.absolutePath)
buildInfo.put(Constants.GENERATED_TOKEN, generatedDir.absolutePath)
buildInfo.put(Constants.FRONTEND_TOKEN, frontendDir.absolutePath)
}
buildInfo.put(Constants.SERVLET_PARAMETER_ENABLE_DEV_SERVER, !productionMode)
tokenFile.ensureParentDirsCreated()
Updater.writeJson(tokenFile, buildInfo)
tokenFile.writeText(JsonUtil.stringify(buildInfo, 2) + "\n", Charsets.UTF_8)
// println("\t\tToken content:\n ${tokenFile.readText(Charsets.UTF_8)}")
}
// now run NodeUpdater
println("\tBuilding frontend...")
// enablePackagesUpdate OR enableImportsUpdate
println("\t\tGenerating web-components into $generatedDir")
val gen = com.vaadin.flow.server.frontend.FrontendWebComponentGenerator(customClassFinder)
gen.generateWebComponents(generatedDir)
run {
// TaskUpdatePackages
println("\tUpdating package dependency and hash information")
// this cannot be refactored out! (as tempting as that might be...)
val frontendDependencies = FrontendDependenciesScanner.FrontendDependenciesScannerFactory()
.createScanner(false, customClassFinder, true)
val packageJson = Updater.getOrCreateJson(generatedJsonPackageFile)
val updateResult = Updater.updateGeneratedPackageJsonDependencies(
packageJson,
frontendDependencies.packages,
jsonPackageFile, generatedJsonPackageFile, nodeModulesDir, jsonPackageLockFile
)
val isModified = updateResult.first
if (isModified) {
Updater.writeJson(generatedJsonPackageFile, packageJson)
}
val hashIsModified = Updater.updatePackageHash(jsonPackageFile, packageJson)
if (hashIsModified) {
println("\t\tPackage dependencies were modified!")
}
val doCleanup = updateResult.second
if (doCleanup) {
println("\t\t##########################################################")
println("\t\tNode.js information is different. Cleaning up directories!")
println("\t\t##########################################################")
// Removes package-lock.json file and node_modules folders in case the versions are different.
if (jsonPackageLockFile.exists()) {
if (!jsonPackageLockFile.delete()) {
throw GradleException(
"Could not remove ${jsonPackageLockFile.path} file. This file has been generated with " +
"a different platform version. Try to remove it manually."
)
}
}
if (nodeModulesDir.exists()) {
nodeModulesDir.deleteRecursively()
}
val generatedNodeModules = buildDir.resolve(FrontendUtils.NODE_MODULES)
if (generatedNodeModules.exists()) {
generatedNodeModules.deleteRecursively()
}
}
// also have to make sure that webpack is properly installed!
val webPackNotInstalled = !webPackExecutableFile.canRead()
if (doCleanup || isModified || hashIsModified || webPackNotInstalled) {
println("\t\tSomething changed, installing dependencies")
// must run AFTER package.json file is created **AND** packages are updated!
installPackageDependencies()
// for node_modules\@vaadin\vaadin-usage-statistics
//or you can disable vaadin-usage-statistics for the project by adding
//```
// "vaadin": { "disableUsageStatistics": true }
//```
//to your project `package.json` and running `npm install` again (remove `node_modules` if needed).
//
//You can verify this by checking that `vaadin-usage-statistics.js` contains an empty function.
}
}
if (!productionMode) {
// the dev mode initializer from the App Launcher will build everything following, but in a special way
return
}
// copyResources
run {
println("\tCopying resources...")
var start = System.nanoTime()
/////////////////////
val classPathScanResult = io.github.classgraph.ClassGraph()
.overrideClasspath(projectDependencies)
.enableSystemJarsAndModules()
.enableInterClassDependencies()
.enableExternalClasses()
.enableAllInfo()
.scan()
val frontendLocations = mutableSetOf<File>()
// all jar files having files in the 'META-INF/frontend' or 'META-INF/resources/frontend' folder.
// We don't use URLClassLoader because will fail in Java 9+
classPathScanResult.allResources.forEach {
val interiorPath = it.path
val jarContainer = it.classpathElementFile
if (interiorPath.startsWith(Constants.RESOURCES_FRONTEND_DEFAULT) || interiorPath.startsWith(Constants.COMPATIBILITY_RESOURCES_FRONTEND_DEFAULT)) {
frontendLocations.add(jarContainer)
}
}
classPathScanResult.close()
/////////////////////
val targetDirectory = nodeModulesDir.resolve(FrontendUtils.FLOW_NPM_PACKAGE_NAME)
targetDirectory.mkdirs()
println("\tCopying frontend resources to '$targetDirectory'")
// get all jar files having files in the 'META-INF/frontend' or 'META-INF/resources/frontend' folder.
println("\t\tFound ${frontendLocations.size} resources")
frontendLocations.forEach {
println("\t\t\t${it.name}")
}
// copy jar resources
@Suppress("LocalVariableName")
val WILDCARD_INCLUSIONS = arrayOf("**/*.js", "**/*.css")
val jarContentsManager = com.vaadin.flow.server.frontend.JarContentsManager()
frontendLocations.forEach { location ->
jarContentsManager.copyIncludedFilesFromJarTrimmingBasePath(
location, Constants.RESOURCES_FRONTEND_DEFAULT,
targetDirectory, *WILDCARD_INCLUSIONS
)
jarContentsManager.copyIncludedFilesFromJarTrimmingBasePath(
location, Constants.COMPATIBILITY_RESOURCES_FRONTEND_DEFAULT,
targetDirectory, *WILDCARD_INCLUSIONS
)
}
var ms = (System.nanoTime() - start) / 1000000
println("\t\tCopied ${frontendLocations.size} resources in $ms ms")
// copy Local Resources
if (frontendDir.isDirectory) {
start = System.nanoTime()
frontendDir.absoluteFile
.copyRecursively(targetDirectory, true)
ms = (System.nanoTime() - start) / 1000000
println("\t\tCopied frontend directory $frontendDir")
println("\t\tCopied frontend directory in $ms ms")
} else {
println("\t\tFound no local frontend resources for the project")
}
}
// enableImportsUpdate
run {
val start = System.nanoTime()
println("\tGenerating vaadin flow files...")
val genFile = generatedFilesDir.resolve("generated-flow-imports.js")
val genFallbackFile = generatedFilesDir.resolve("generated-flow-imports-fallback.js")
println("\t\tGenerating $genFile")
println("\t\tGenerating $genFallbackFile")
val frontendDependencies = FrontendDependenciesScanner.FrontendDependenciesScannerFactory()
.createScanner(false, customClassFinder, true)
val provider =
com.vaadin.flow.function.SerializableFunction<com.vaadin.flow.server.frontend.scanner.ClassFinder, com.vaadin.flow.server.frontend.scanner.FrontendDependenciesScanner> { t ->
FrontendDependenciesScanner.FrontendDependenciesScannerFactory().createScanner(true, t, true)
}
val taskUpdateClass = com.vaadin.flow.server.frontend.TaskUpdateImports::class.java
val constructor = taskUpdateClass.declaredConstructors.first { constructor -> constructor.parameterCount == 8 }
constructor.isAccessible = true
val updateImports = constructor.newInstance(
customClassFinder, frontendDependencies, provider,
jsonPackageFile.parentFile, // folder with the `package.json` file
generatedFilesDir, // folder where flow generated files will be placed.
frontendDir, // a directory with project's frontend files
tokenFile, Updater.getJson(tokenFile)
) as com.vaadin.flow.server.frontend.TaskUpdateImports
updateImports.execute()
val ms = (System.nanoTime() - start) / 1000000
println("\t\tFinished in $ms ms")
}
run {
val start = System.nanoTime()
println("\tConfiguring WebPack")
val nodeBinDirProvider = variantComputer.computeNodeBinDir(nodeDirProvider)
val nodeExec = variantComputer.computeNodeExec(nodeExtension, nodeBinDirProvider).get()
val npmDir = variantComputer.computeNpmDir(nodeExtension, nodeDirProvider)
val npmBinDir = variantComputer.computeNpmBinDir(npmDir).get().asFile.absolutePath
val nodeBinDir = nodeBinDirProvider.get().asFile.absolutePath
val nodePath = npmBinDir + File.pathSeparator + nodeBinDir
// For information about webpack, SEE https://webpack.js.org/guides/getting-started/
val exec = Exec(project)
exec.executable = nodeExec
exec.path = nodePath
exec.workingDir = variantComputer.computeNodeModulesDir(nodeExtension).get().asFile
exec.workingDir = buildDir
exec.arguments = listOf(webPackExecutableFile.path, "--config", webPackProdFile.absolutePath, "--silent")
exec.suppressOutput = false
exec.debug = false
exec.execute()
val ms = (System.nanoTime() - start) / 1000000
println("\t\tFinished in $ms ms")
}
} catch (e: Exception) {
throw GradleException(e.message ?: "", e)
} finally {
customClassFinder.finish()
}
}
fun installPackageDependencies() {
// now we have to install the dependencies from package.json! We do this MANUALLY, instead of using the builder
println("\tInstalling package dependencies --- FIXEME")
// val packageInstallExec = getExec()
// packageInstallExec.environment["ADBLOCK"] = "1"
// packageInstallExec.environment["NO_UPDATE_NOTIFIER"] = "1"
// packageInstallExec.arguments = listOf(npmScriptFile, "install")
// packageInstallExec.execute()
}
/**
* Recursively resolves all child dependencies of the project
*
* THIS MUST BE IN "afterEvaluate" or run from a specific task.
*/
fun resolveAllDependencies(project: Project): List<DependencyScanner.DependencyData> {
// NOTE: we cannot createTree("compile") and createTree("runtime") using the same exitingNames and expect correct results.
// This is because a dependency might exist for compile and runtime, but have different children, therefore, the list
// will be incomplete
// there will be DUPLICATES! (we don't care about children or hierarchy, so we remove the dupes)
return (DependencyScanner.scan(project, "compileClasspath") +
DependencyScanner.scan(project, "runtimeClasspath")
).toSet().toList()
}
/**
* Recursively resolves all child compile dependencies of the project
*
* THIS MUST BE IN "afterEvaluate" or run from a specific task.
*/
fun resolveRuntimeDependencies(project: Project): DependencyScanner.ProjectDependencies {
val projectDependencies = mutableListOf<DependencyScanner.Dependency>()
val existingNames = mutableMapOf<String, DependencyScanner.Dependency>()
DependencyScanner.createTree(project, "runtimeClasspath", projectDependencies, existingNames)
return DependencyScanner.ProjectDependencies(projectDependencies, existingNames.map { it.value })
}
}
}

View File

@ -0,0 +1,60 @@
/*
* Copyright 2020 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.gradleVaadin
import org.gradle.api.Project
import org.gradle.api.publish.PublishingExtension
import org.gradle.api.publish.maven.MavenPom
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.tasks.Input
import org.gradle.internal.impldep.org.apache.maven.model.Developer
import org.gradle.internal.impldep.org.apache.maven.model.IssueManagement
import org.gradle.plugins.signing.Sign
import org.gradle.plugins.signing.SigningExtension
import org.gradle.plugins.signing.signatory.internal.pgp.InMemoryPgpSignatoryProvider
import java.io.File
import java.security.PrivateKey
import java.time.Duration
open class VaadinConfig(private val project: Project): java.io.Serializable {
/**
* Version of node to download and install
*/
@get:Input
var nodeVersion = "13.7.0"
set(value) {
field = value
NodeExtension[project].version.set(value)
}
@get:Input
var vaadinVersion = "14.1.17"
@get:Input
var nodeDir = project.buildDir
set(value) {
field = value
// NodeExtension[project].test.set(value)
}
// @get:Input
// var test = "aaaa"
// set(value) {
// field = value
// NodeExtension[project].test.set(value)
// }
}

View File

@ -0,0 +1,34 @@
/*
* Copyright 2020 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.gradleVaadin
import org.gradle.api.*
import org.gradle.api.provider.Provider
import org.gradle.api.services.BuildService
import org.gradle.api.tasks.TaskAction
open class
VaadinConfigTask : DefaultTask() {
init {
outputs.upToDateWhen { false }
outputs.cacheIf { false }
description = "Configure Vaadin"
}
@TaskAction
fun run() {
}
}

View File

@ -0,0 +1,227 @@
/*
* Copyright 2021 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.gradleVaadin.node.deps
import org.gradle.api.Project
import org.gradle.api.artifacts.ResolvedArtifact
import org.gradle.api.artifacts.ResolvedDependency
import java.io.File
import java.util.*
object DependencyScanner {
/**
* THIS MUST BE IN "afterEvaluate" or run from a specific task.
*
* NOTE: it is possible, when we have a project DEPEND on an older version of that project (ie: bootstrapped from an older version)
* we can have quite deep recursion. A project can never depend on itself, but we check if a project has already been added, and
* don't parse it more than once
*
* This is an actual problem...
*/
fun scan(project: Project, configurationName: String, includeChildren: Boolean = true): List<DependencyData> {
val projectDependencies = mutableListOf<DependencyData>()
val config = project.configurations.getByName(configurationName)
if (!config.isCanBeResolved) {
return projectDependencies
}
try {
config.resolve()
} catch (e: Throwable) {
println("Unable to resolve the $configurationName configuration for the project ${project.name}")
}
val list = LinkedList<ResolvedDependency>()
config.resolvedConfiguration.lenientConfiguration.getFirstLevelModuleDependencies(org.gradle.api.specs.Specs.SATISFIES_ALL).forEach { dep ->
list.add(dep)
}
var next: ResolvedDependency
while (list.isNotEmpty()) {
next = list.poll()
val module = next.module.id
val group = module.group
val name = module.name
val version = module.version
val mavenId = "$group:$name:$version"
val artifacts = mutableListOf<Artifact>()
next.moduleArtifacts.forEach { artifact: ResolvedArtifact ->
try {
val artifactModule = artifact.moduleVersion.id
artifacts.add(Artifact(artifactModule.group, artifactModule.name, artifactModule.version, artifact.file.absoluteFile))
} catch (e: Exception) {
println("Error getting artifact for $mavenId, file: ${artifact.file.absoluteFile}")
}
}
projectDependencies.add(DependencyData(group, name, version, artifacts))
if (includeChildren) {
list.addAll(next.children)
}
}
return projectDependencies
}
/**
* THIS MUST BE IN "afterEvaluate" or run from a specific task.
*
* NOTE: it is possible, when we have a project DEPEND on an older version of that project (ie: bootstrapped from an older version)
* we can have quite deep recursion. A project can never depend on itself, but we check if a project has already been added, and
* don't parse it more than once
*
* This is an actual problem...
*/
fun createTree(
project: Project,
configurationName: String,
projectDependencies: MutableList<Dependency> = mutableListOf(),
existingDeps: MutableMap<String, Dependency> = mutableMapOf(),
): List<Dependency> {
val config = project.configurations.getByName(configurationName)
if (!config.isCanBeResolved) {
return projectDependencies
}
try {
config.resolve()
} catch (e: Throwable) {
println("Unable to resolve the $configurationName configuration for the project ${project.name}")
}
// the root parent is tossed out, but not the topmost list of dependencies
val rootParent = Dependency("", "", "", listOf(), projectDependencies)
val parentList = LinkedList<Dependency>()
val list = LinkedList<ResolvedDependency>()
config.resolvedConfiguration.lenientConfiguration.getFirstLevelModuleDependencies(org.gradle.api.specs.Specs.SATISFIES_ALL).forEach { dep ->
list.add(dep)
parentList.add(rootParent)
}
var next: ResolvedDependency
while (list.isNotEmpty()) {
next = list.poll()
val module = next.module.id
val group = module.group
val name = module.name
val version = module.version
val mavenId = "$group:$name:$version"
if (!existingDeps.containsKey(mavenId)) {
val artifacts = mutableListOf<Artifact>()
next.moduleArtifacts.forEach { artifact: ResolvedArtifact ->
try {
val artifactModule = artifact.moduleVersion.id
artifacts.add(Artifact(artifactModule.group, artifactModule.name, artifactModule.version, artifact.file.absoluteFile))
} catch (e: Exception) {
println("Error getting artifact for $mavenId, file: ${artifact.file.absoluteFile}")
}
}
val dependency = Dependency(group, name, version, artifacts, mutableListOf())
// now add to our parent
val parent = parentList.poll()
(parent.children as MutableList).add(dependency)
next.children.forEach { child ->
parentList.add(dependency)
list.add(child)
}
existingDeps[mavenId] = dependency
}
}
return projectDependencies
}
/**
* Flatten the dependency children
*/
fun flattenDeps(dep: Dependency): List<Dependency> {
val flatDeps = mutableSetOf<Dependency>()
flattenDep(dep, flatDeps)
return flatDeps.toList()
}
private fun flattenDep(dep: Dependency, flatDeps: MutableSet<Dependency>) {
flatDeps.add(dep)
dep.children.forEach {
flattenDep(it, flatDeps)
}
}
data class ProjectDependencies(val tree: List<Dependency>, val dependencies: List<Dependency>)
data class DependencyData(
val group: String,
val name: String,
val version: String,
val artifacts: List<Artifact>
) {
fun mavenId(): String {
return "$group:$name:$version"
}
override fun toString(): String {
return mavenId()
}
}
data class Dependency(
val group: String,
val name: String,
val version: String,
val artifacts: List<Artifact>,
val children: List<Dependency>
) {
fun mavenId(): String {
return "$group:$name:$version"
}
override fun toString(): String {
return mavenId()
}
}
data class Artifact(val group: String, val name: String, val version: String, val file: File) {
val id: String
get() {
return "$group:$name:$version"
}
}
data class Maven(val group: String, val name: String, val version: String = "") {
val id: String
get() {
return "$group:$name:$version"
}
}
}

View File

@ -0,0 +1,15 @@
package dorkbox.gradleVaadin.node.exec
import org.gradle.api.Action
import org.gradle.process.ExecSpec
import java.io.File
internal data class ExecConfiguration(
val executable: String,
val args: List<String> = listOf(),
val additionalBinPaths: List<String> = listOf(),
val environment: Map<String, String> = mapOf(),
val workingDir: File? = null,
val ignoreExitValue: Boolean = false,
val execOverrides: Action<ExecSpec>? = null
)

View File

@ -0,0 +1,41 @@
package dorkbox.gradleVaadin.node.exec
import dorkbox.gradleVaadin.NodeExtension
import dorkbox.gradleVaadin.node.util.ProjectApiHelper
import org.gradle.api.file.DirectoryProperty
import java.io.File
internal class ExecRunner {
fun execute(projectHelper: ProjectApiHelper, extension: NodeExtension, execConfiguration: ExecConfiguration) {
projectHelper.exec {
executable = execConfiguration.executable
args = execConfiguration.args
environment = computeEnvironment(execConfiguration)
isIgnoreExitValue = execConfiguration.ignoreExitValue
workingDir = computeWorkingDir(extension.nodeProjectDir, execConfiguration)
execConfiguration.execOverrides?.execute(this)
}
}
private fun computeEnvironment(execConfiguration: ExecConfiguration): Map<String, String> {
val execEnvironment = mutableMapOf<String, String>()
execEnvironment += System.getenv()
execEnvironment += execConfiguration.environment
if (execConfiguration.additionalBinPaths.isNotEmpty()) {
// Take care of Windows environments that may contain "Path" OR "PATH" - both existing
// possibly (but not in parallel as of now)
val pathEnvironmentVariableName = if (execEnvironment["Path"] != null) "Path" else "PATH"
val actualPath = execEnvironment[pathEnvironmentVariableName]
val additionalPathsSerialized = execConfiguration.additionalBinPaths.joinToString(File.pathSeparator)
execEnvironment[pathEnvironmentVariableName] =
"${additionalPathsSerialized}${File.pathSeparator}${actualPath}"
}
return execEnvironment
}
private fun computeWorkingDir(nodeProjectDir: DirectoryProperty, execConfiguration: ExecConfiguration): File? {
val workingDir = execConfiguration.workingDir ?: nodeProjectDir.get().asFile
workingDir.mkdirs()
return workingDir
}
}

View File

@ -0,0 +1,13 @@
package dorkbox.gradleVaadin.node.exec
import org.gradle.api.Action
import org.gradle.process.ExecSpec
import java.io.File
internal data class NodeExecConfiguration(
val command: List<String> = listOf(),
val environment: Map<String, String> = mapOf(),
val workingDir: File? = null,
val ignoreExitValue: Boolean = false,
val execOverrides: Action<ExecSpec>? = null
)

View File

@ -0,0 +1,39 @@
package dorkbox.gradleVaadin.node.exec
import dorkbox.gradleVaadin.NodeExtension
import dorkbox.gradleVaadin.node.util.ProjectApiHelper
import dorkbox.gradleVaadin.node.util.zip
import dorkbox.gradleVaadin.node.variant.VariantComputer
import org.gradle.api.file.Directory
import org.gradle.api.provider.Provider
internal class NodeExecRunner {
fun execute(project: ProjectApiHelper, extension: NodeExtension, nodeExecConfiguration: NodeExecConfiguration) {
val execConfiguration = buildExecConfiguration(extension, nodeExecConfiguration).get()
val execRunner = ExecRunner()
execRunner.execute(project, extension, execConfiguration)
}
private fun buildExecConfiguration(nodeExtension: NodeExtension, nodeExecConfiguration: NodeExecConfiguration):
Provider<ExecConfiguration> {
val variantComputer = VariantComputer()
val nodeDirProvider = variantComputer.computeNodeDir(nodeExtension)
val nodeBinDirProvider = variantComputer.computeNodeBinDir(nodeDirProvider)
val executableProvider = variantComputer.computeNodeExec(nodeExtension, nodeBinDirProvider)
val additionalBinPathProvider = computeAdditionalBinPath(nodeExtension, nodeBinDirProvider)
return zip(executableProvider, additionalBinPathProvider)
.map { (executable, additionalBinPath) ->
ExecConfiguration(executable, nodeExecConfiguration.command, additionalBinPath,
nodeExecConfiguration.environment, nodeExecConfiguration.workingDir,
nodeExecConfiguration.ignoreExitValue, nodeExecConfiguration.execOverrides)
}
}
private fun computeAdditionalBinPath(nodeExtension: NodeExtension, nodeBinDirProvider: Provider<Directory>):
Provider<List<String>> {
return zip(nodeExtension.download, nodeBinDirProvider)
.map { (download, nodeBinDir) ->
if (download) listOf(nodeBinDir.asFile.absolutePath) else listOf()
}
}
}

View File

@ -0,0 +1,14 @@
package dorkbox.gradleVaadin.node.npm.exec
import dorkbox.gradleVaadin.NodeExtension
import dorkbox.gradleVaadin.node.variant.VariantComputer
import org.gradle.api.file.Directory
import org.gradle.api.provider.Provider
internal typealias CommandExecComputer = (variantComputer: VariantComputer, nodeExtension: NodeExtension,
npmBinDir: Provider<Directory>) -> Provider<String>
internal data class NpmExecConfiguration(
val command: String,
val commandExecComputer: CommandExecComputer
)

View File

@ -0,0 +1,123 @@
package dorkbox.gradleVaadin.node.npm.exec
import dorkbox.gradleVaadin.NodeExtension
import dorkbox.gradleVaadin.node.exec.ExecConfiguration
import dorkbox.gradleVaadin.node.exec.ExecRunner
import dorkbox.gradleVaadin.node.exec.NodeExecConfiguration
import dorkbox.gradleVaadin.node.npm.proxy.NpmProxy
import dorkbox.gradleVaadin.node.npm.proxy.NpmProxy.Companion.computeNpmProxyEnvironmentVariables
import dorkbox.gradleVaadin.node.util.ProjectApiHelper
import dorkbox.gradleVaadin.node.util.zip
import dorkbox.gradleVaadin.node.variant.VariantComputer
import org.gradle.api.provider.Provider
import org.gradle.api.provider.ProviderFactory
import java.io.File
import javax.inject.Inject
internal abstract class NpmExecRunner {
@get:Inject
abstract val providers: ProviderFactory
private val variantComputer = VariantComputer()
fun executeNpmCommand(project: ProjectApiHelper, extension: NodeExtension, nodeExecConfiguration: NodeExecConfiguration) {
val npmExecConfiguration = NpmExecConfiguration("npm"
) { variantComputer, nodeExtension, npmBinDir -> variantComputer.computeNpmExec(nodeExtension, npmBinDir) }
executeCommand(project, extension, addProxyEnvironmentVariables(extension, nodeExecConfiguration),
npmExecConfiguration)
}
private fun addProxyEnvironmentVariables(nodeExtension: NodeExtension,
nodeExecConfiguration: NodeExecConfiguration
): NodeExecConfiguration {
if (NpmProxy.shouldConfigureProxy(System.getenv(), nodeExtension.nodeProxySettings.get())) {
val npmProxyEnvironmentVariables = computeNpmProxyEnvironmentVariables()
if (npmProxyEnvironmentVariables.isNotEmpty()) {
val environmentVariables =
nodeExecConfiguration.environment.plus(npmProxyEnvironmentVariables)
return nodeExecConfiguration.copy(environment = environmentVariables)
}
}
return nodeExecConfiguration
}
fun executeNpxCommand(project: ProjectApiHelper, extension: NodeExtension, nodeExecConfiguration: NodeExecConfiguration) {
val npxExecConfiguration = NpmExecConfiguration("npx") { variantComputer, nodeExtension, npmBinDir ->
variantComputer.computeNpxExec(nodeExtension, npmBinDir)
}
executeCommand(project, extension, nodeExecConfiguration, npxExecConfiguration)
}
private fun executeCommand(project: ProjectApiHelper, extension: NodeExtension, nodeExecConfiguration: NodeExecConfiguration,
npmExecConfiguration: NpmExecConfiguration
) {
val execConfiguration =
computeExecConfiguration(extension, npmExecConfiguration, nodeExecConfiguration).get()
val execRunner = ExecRunner()
execRunner.execute(project, extension, execConfiguration)
}
private fun computeExecConfiguration(extension: NodeExtension, npmExecConfiguration: NpmExecConfiguration,
nodeExecConfiguration: NodeExecConfiguration
): Provider<ExecConfiguration> {
val additionalBinPathProvider = computeAdditionalBinPath(extension)
val executableAndScriptProvider = computeExecutable(extension, npmExecConfiguration)
return zip(additionalBinPathProvider, executableAndScriptProvider)
.map { (additionalBinPath, executableAndScript) ->
val argsPrefix =
if (executableAndScript.script != null) listOf(executableAndScript.script) else listOf()
val args = argsPrefix.plus(nodeExecConfiguration.command)
ExecConfiguration(executableAndScript.executable, args, additionalBinPath,
nodeExecConfiguration.environment, nodeExecConfiguration.workingDir,
nodeExecConfiguration.ignoreExitValue, nodeExecConfiguration.execOverrides)
}
}
private fun computeExecutable(nodeExtension: NodeExtension, npmExecConfiguration: NpmExecConfiguration):
Provider<ExecutableAndScript> {
val nodeDirProvider = variantComputer.computeNodeDir(nodeExtension)
val npmDirProvider = variantComputer.computeNpmDir(nodeExtension, nodeDirProvider)
val nodeBinDirProvider = variantComputer.computeNodeBinDir(nodeDirProvider)
val npmBinDirProvider = variantComputer.computeNpmBinDir(npmDirProvider)
val nodeExecProvider = variantComputer.computeNodeExec(nodeExtension, nodeBinDirProvider)
val executableProvider =
npmExecConfiguration.commandExecComputer(variantComputer, nodeExtension, npmBinDirProvider)
val npmScriptFileProvider =
variantComputer.computeNpmScriptFile(nodeDirProvider, npmExecConfiguration.command)
return zip(nodeExtension.download, nodeExtension.nodeProjectDir, executableProvider, nodeExecProvider,
npmScriptFileProvider).map {
val (download, nodeProjectDir, executable, nodeExec,
npmScriptFile) = it
if (download) {
val localCommandScript = nodeProjectDir.dir("node_modules/npm/bin")
.file("${npmExecConfiguration.command}-cli.js").asFile
if (localCommandScript.exists()) {
return@map ExecutableAndScript(nodeExec, localCommandScript.absolutePath)
} else if (!File(executable).exists()) {
return@map ExecutableAndScript(nodeExec, npmScriptFile)
}
}
return@map ExecutableAndScript(executable)
}
}
private data class ExecutableAndScript(
val executable: String,
val script: String? = null
)
private fun computeAdditionalBinPath(nodeExtension: NodeExtension): Provider<List<String>> {
return nodeExtension.download.flatMap { download ->
if (!download) {
providers.provider { listOf<String>() }
}
val nodeDirProvider = variantComputer.computeNodeDir(nodeExtension)
val nodeBinDirProvider = variantComputer.computeNodeBinDir(nodeDirProvider)
val npmDirProvider = variantComputer.computeNpmDir(nodeExtension, nodeDirProvider)
val npmBinDirProvider = variantComputer.computeNpmBinDir(npmDirProvider)
zip(npmBinDirProvider, nodeBinDirProvider).map { (npmBinDir, nodeBinDir) ->
listOf(npmBinDir, nodeBinDir).map { file -> file.asFile.absolutePath }
}
}
}
}

View File

@ -0,0 +1,104 @@
package dorkbox.gradleVaadin.node.npm.proxy
import java.net.URLEncoder
import java.util.stream.Collectors.toList
import java.util.stream.Stream
import kotlin.text.Charsets.UTF_8
internal class NpmProxy {
companion object {
// These are the environment variables that HTTPing applications checks, proxy is on and off.
// FTP skipped in hopes of a better future.
private val proxyVariables = listOf(
"HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY", "PROXY"
)
// And since npm also takes settings in the form of environment variables with the
// NPM_CONFIG_<setting> format, we should check those. Hopefully nobody does this.
private val npmProxyVariables = listOf(
"NPM_CONFIG_PROXY", "NPM_CONFIG_HTTPS-PROXY", "NPM_CONFIG_NOPROXY"
)
fun computeNpmProxyEnvironmentVariables(): Map<String, String> {
val proxyEnvironmentVariables = computeProxyUrlEnvironmentVariables()
if (proxyEnvironmentVariables.isNotEmpty()) {
addProxyIgnoredHostsEnvironmentVariable(proxyEnvironmentVariables)
}
return proxyEnvironmentVariables.toMap()
}
fun shouldConfigureProxy(env: Map<String, String>, settings: ProxySettings): Boolean {
if (settings == ProxySettings.FORCED) {
return true
} else if (settings == ProxySettings.SMART) {
return !hasProxyConfiguration(env)
}
return false
}
/**
* Returns true if the given map of environment variables already has
* proxy settings configured.
*
* @param env map of environment variables
*/
fun hasProxyConfiguration(env: Map<String, String>): Boolean {
return env.keys.any {
proxyVariables.contains(it.toUpperCase()) || npmProxyVariables.contains(it.toUpperCase())
}
}
private fun computeProxyUrlEnvironmentVariables(): MutableMap<String, String> {
val proxyArgs = mutableMapOf<String, String>()
for ((proxyProto, proxyParam) in
listOf(arrayOf("http", "HTTP_PROXY"), arrayOf("https", "HTTPS_PROXY"))) {
var proxyHost = System.getProperty("$proxyProto.proxyHost")
val proxyPort = System.getProperty("$proxyProto.proxyPort")
if (proxyHost != null && proxyPort != null) {
proxyHost = proxyHost.replace("^https?://".toRegex(), "")
val proxyUser = System.getProperty("$proxyProto.proxyUser")
val proxyPassword = System.getProperty("$proxyProto.proxyPassword")
if (proxyUser != null && proxyPassword != null) {
proxyArgs[proxyParam] =
"http://${encode(proxyUser)}:${encode(proxyPassword)}@$proxyHost:$proxyPort"
} else {
proxyArgs[proxyParam] = "http://$proxyHost:$proxyPort"
}
}
}
return proxyArgs
}
private fun encode(value: String): String {
return URLEncoder.encode(value, UTF_8.toString())
}
private fun addProxyIgnoredHostsEnvironmentVariable(proxyEnvironmentVariables: MutableMap<String, String>) {
val proxyIgnoredHosts = computeProxyIgnoredHosts()
if (proxyIgnoredHosts.isNotEmpty()) {
proxyEnvironmentVariables["NO_PROXY"] = proxyIgnoredHosts.joinToString(", ")
}
}
private fun computeProxyIgnoredHosts(): List<String> {
return Stream.of("http.nonProxyHosts", "https.nonProxyHosts")
.map { property ->
val propertyValue = System.getProperty(property)
if (propertyValue != null) {
val hosts = propertyValue.split("|")
return@map hosts
.map { host ->
if (host.contains(":")) host.split(":")[0]
else host
}
}
return@map listOf<String>()
}
.flatMap(List<String>::stream)
.distinct()
.collect(toList())
}
}
}

View File

@ -0,0 +1,23 @@
package dorkbox.gradleVaadin.node.npm.proxy
/**
* @since 3.0
*/
enum class ProxySettings {
/**
* The default, this will set the proxy settings only if there's no configuration
* present in the environment variables already.
*/
SMART,
/**
* This will always set the proxy settings, overriding any settings already present.
* This might cause incorrect settings.
*/
FORCED,
/**
* Don't set any proxy settings.
*/
OFF
}

View File

@ -0,0 +1,105 @@
package dorkbox.gradleVaadin.node.npm.task
import dorkbox.gradleVaadin.node.util.zip
import dorkbox.gradleVaadin.Vaadin
import org.gradle.api.Action
import org.gradle.api.file.ConfigurableFileTree
import org.gradle.api.file.Directory
import org.gradle.api.file.FileTree
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.*
import org.gradle.kotlin.dsl.property
import org.gradle.api.tasks.PathSensitivity.RELATIVE
import java.io.File
/**
* npm install that only gets executed if gradle decides so.
*/
abstract class NpmInstallTask : NpmTask() {
@get:Internal
val nodeModulesOutputFilter =
objects.property<Action<ConfigurableFileTree>>()
init {
group = Vaadin.NPM_GROUP
description = "Install node packages from package.json."
dependsOn(NpmSetupTask.NAME)
npmCommand.set(nodeExtension.npmInstallCommand.map { listOf(it) })
}
@PathSensitive(RELATIVE)
@InputFile
protected fun getPackageJsonFile(): Provider<File> {
return projectFileIfExists("package.json")
}
@PathSensitive(RELATIVE)
@Optional
@InputFile
protected fun getNpmShrinkwrap(): Provider<File> {
return projectFileIfExists("npm-shrinkwrap.json")
}
@PathSensitive(RELATIVE)
@Optional
@InputFile
protected fun getPackageLockFileAsInput(): Provider<File> {
return npmCommand.flatMap { command ->
if (command[0] == "ci") projectFileIfExists("package-lock.json") else providers.provider { null }
}
}
@PathSensitive(RELATIVE)
@Optional
@InputFile
protected fun getYarnLockFile(): Provider<File> {
return projectFileIfExists("yarn.lock")
}
@Optional
@OutputFile
protected fun getPackageLockFileAsOutput(): Provider<File> {
return npmCommand.flatMap { command ->
if (command[0] == "install") projectFileIfExists("package-lock.json") else providers.provider { null }
}
}
private fun projectFileIfExists(name: String): Provider<File> {
return nodeExtension.nodeProjectDir.map { it.file(name).asFile }
.flatMap { if (it.exists()) providers.provider { it } else providers.provider { null } }
}
@Optional
@OutputDirectory
@Suppress("unused")
protected fun getNodeModulesDirectory(): Provider<Directory> {
val filter = nodeModulesOutputFilter.orNull
return if (filter == null) nodeExtension.nodeProjectDir.dir("node_modules")
else providers.provider { null }
}
@Optional
@OutputFiles
@Suppress("unused")
protected fun getNodeModulesFiles(): Provider<FileTree> {
val nodeModulesDirectoryProvider = nodeExtension.nodeProjectDir.dir("node_modules")
return zip(nodeModulesDirectoryProvider, nodeModulesOutputFilter)
.flatMap { (nodeModulesDirectory, nodeModulesOutputFilter) ->
if (nodeModulesOutputFilter != null) {
val fileTree = projectHelper.fileTree(nodeModulesDirectory)
nodeModulesOutputFilter.execute(fileTree)
providers.provider { fileTree }
} else providers.provider { null }
}
}
// For DSL
@Suppress("unused")
fun nodeModulesOutputFilter(nodeModulesOutputFilter: Action<ConfigurableFileTree>) {
this.nodeModulesOutputFilter.set(nodeModulesOutputFilter)
}
companion object {
const val NAME = "npmInstall"
}
}

View File

@ -0,0 +1,91 @@
package dorkbox.gradleVaadin.node.npm.task
import dorkbox.gradleVaadin.NodeExtension
import dorkbox.gradleVaadin.Vaadin
import dorkbox.gradleVaadin.node.exec.NodeExecConfiguration
import dorkbox.gradleVaadin.node.npm.exec.NpmExecRunner
import dorkbox.gradleVaadin.node.task.NodeSetupTask
import dorkbox.gradleVaadin.node.util.ProjectApiHelper
import dorkbox.gradleVaadin.node.variant.VariantComputer
import org.gradle.api.DefaultTask
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Provider
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
import org.gradle.kotlin.dsl.listProperty
import java.io.File
import javax.inject.Inject
/**
* npm install that only gets executed if gradle decides so.
*/
abstract class NpmSetupTask : DefaultTask() {
@get:Inject
abstract val objects: ObjectFactory
@get:Inject
abstract val providers: ProviderFactory
@get:Internal
protected val nodeExtension = NodeExtension[project]
@get:Internal
val projectHelper = ProjectApiHelper.newInstance(project)
@get:Input
val args = objects.listProperty<String>()
@get:Input
val download = nodeExtension.download
@get:OutputDirectory
val npmDir by lazy {
val variantComputer = VariantComputer()
val nodeDir = variantComputer.computeNodeDir(nodeExtension)
variantComputer.computeNpmDir(nodeExtension, nodeDir)
}
init {
group = Vaadin.NPM_GROUP
description = "Setup a specific version of npm to be used by the build."
dependsOn(NodeSetupTask.NAME)
onlyIf {
isTaskEnabled()
}
}
@Input
protected open fun getVersion(): Provider<String> {
return nodeExtension.npmVersion
}
@Internal
open fun isTaskEnabled(): Boolean {
return nodeExtension.npmVersion.get().isNotBlank()
}
@TaskAction
fun exec() {
val command = computeCommand()
val nodeExecConfiguration = NodeExecConfiguration(command)
val npmExecRunner = objects.newInstance(NpmExecRunner::class.java)
npmExecRunner.executeNpmCommand(projectHelper, nodeExtension, nodeExecConfiguration)
}
protected open fun computeCommand(): List<String> {
val version = nodeExtension.npmVersion.get()
val directory = npmDir.get().asFile
// npm < 7 creates the directory if it's missing, >= 7 fails if it's missing
File(directory, "lib").mkdirs()
return listOf("install", "--global", "--no-save", "--prefix", directory.absolutePath,
"npm@$version") + args.get()
}
companion object {
const val NAME = "npmSetup"
}
}

View File

@ -0,0 +1,75 @@
package dorkbox.gradleVaadin.node.npm.task
import dorkbox.gradleVaadin.NodeExtension
import dorkbox.gradleVaadin.Vaadin
import dorkbox.gradleVaadin.node.exec.NodeExecConfiguration
import dorkbox.gradleVaadin.node.npm.exec.NpmExecRunner
import dorkbox.gradleVaadin.node.util.ProjectApiHelper
import org.gradle.api.Action
import org.gradle.api.DefaultTask
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.TaskAction
import org.gradle.kotlin.dsl.listProperty
import org.gradle.kotlin.dsl.mapProperty
import org.gradle.kotlin.dsl.property
import org.gradle.process.ExecSpec
import javax.inject.Inject
abstract class NpmTask : DefaultTask() {
@get:Inject
abstract val objects: ObjectFactory
@get:Inject
abstract val providers: ProviderFactory
@get:Optional
@get:Input
val npmCommand = objects.listProperty<String>()
@get:Optional
@get:Input
val args = objects.listProperty<String>()
@get:Input
val ignoreExitValue = objects.property<Boolean>().convention(false)
@get:Internal
val workingDir = objects.fileProperty()
@get:Input
val environment = objects.mapProperty<String, String>()
@get:Internal
val execOverrides = objects.property<Action<ExecSpec>>()
@get:Internal
val projectHelper = ProjectApiHelper.newInstance(project)
@get:Internal
val nodeExtension = NodeExtension[project]
init {
group = Vaadin.NPM_GROUP
dependsOn(NpmSetupTask.NAME)
}
// For DSL
@Suppress("unused")
fun execOverrides(execOverrides: Action<ExecSpec>) {
this.execOverrides.set(execOverrides)
}
@TaskAction
fun exec() {
val command = npmCommand.get().plus(args.get())
val nodeExecConfiguration =
NodeExecConfiguration(command, environment.get(), workingDir.asFile.orNull, ignoreExitValue.get(),
execOverrides.orNull)
val npmExecRunner = objects.newInstance(NpmExecRunner::class.java)
npmExecRunner.executeNpmCommand(projectHelper, nodeExtension, nodeExecConfiguration)
}
}

View File

@ -0,0 +1,75 @@
package dorkbox.gradleVaadin.node.npm.task
import dorkbox.gradleVaadin.NodeExtension
import dorkbox.gradleVaadin.Vaadin
import dorkbox.gradleVaadin.node.exec.NodeExecConfiguration
import dorkbox.gradleVaadin.node.npm.exec.NpmExecRunner
import dorkbox.gradleVaadin.node.util.ProjectApiHelper
import org.gradle.api.Action
import org.gradle.api.DefaultTask
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.TaskAction
import org.gradle.kotlin.dsl.listProperty
import org.gradle.kotlin.dsl.mapProperty
import org.gradle.kotlin.dsl.property
import org.gradle.process.ExecSpec
import javax.inject.Inject
abstract class NpxTask : DefaultTask() {
@get:Inject
abstract val objects: ObjectFactory
@get:Inject
abstract val providers: ProviderFactory
@get:Input
val command = objects.property<String>()
@get:Input
val args = objects.listProperty<String>()
@get:Input
val ignoreExitValue = objects.property<Boolean>().convention(false)
@get:Internal
val workingDir = objects.directoryProperty()
@get:Input
val environment = objects.mapProperty<String, String>()
@get:Internal
val execOverrides = objects.property<Action<ExecSpec>>()
@get:Internal
val projectHelper = ProjectApiHelper.newInstance(project)
@get:Internal
val extension = NodeExtension[project]
init {
group = Vaadin.NPM_GROUP
dependsOn(NpmSetupTask.NAME)
}
// For DSL
@Suppress("unused")
fun execOverrides(execOverrides: Action<ExecSpec>) {
this.execOverrides.set(execOverrides)
}
@TaskAction
fun exec() {
val fullCommand: MutableList<String> = mutableListOf()
command.orNull?.let { fullCommand.add(it) }
fullCommand.addAll(args.get())
val nodeExecConfiguration =
NodeExecConfiguration(fullCommand, environment.get(), workingDir.asFile.orNull,
ignoreExitValue.get(), execOverrides.orNull)
val npmExecRunner = objects.newInstance(NpmExecRunner::class.java)
npmExecRunner.executeNpxCommand(projectHelper, extension, nodeExecConfiguration)
}
}

View File

@ -0,0 +1,184 @@
package dorkbox.gradleVaadin.node.task
import com.dorkbox.version.Version
import com.vaadin.flow.server.Constants
import com.vaadin.flow.server.frontend.FrontendUtils
import com.vaadin.flow.server.frontend.FrontendVersion
import dorkbox.executor.Executor
import dorkbox.gradleVaadin.NodeExtension
import dorkbox.gradleVaadin.Vaadin
import dorkbox.gradleVaadin.node.util.PlatformHelper
import dorkbox.gradleVaadin.node.util.PlatformHelper.Companion.validateToolVersion
import dorkbox.gradleVaadin.node.util.ProjectApiHelper
import dorkbox.gradleVaadin.node.variant.VariantComputer
import org.gradle.api.DefaultTask
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.tasks.*
import org.gradle.internal.impldep.org.bouncycastle.asn1.cmc.CMCStatus.success
import java.io.File
import java.nio.file.CopyOption
import java.nio.file.Files
import java.nio.file.Paths
import java.nio.file.StandardCopyOption
import javax.inject.Inject
abstract class NodeSetupTask : DefaultTask() {
@get:Inject
abstract val objects: ObjectFactory
@get:Inject
abstract val providers: ProviderFactory
@get:Internal
internal val variantComputer = VariantComputer()
private val nodeExtension = NodeExtension[project]
@get:Input
val download = nodeExtension.download
@get:InputFile
val nodeArchiveFile = objects.fileProperty()
@get:OutputDirectory
val nodeDir by lazy {
variantComputer.computeNodeDir(nodeExtension)
}
@get:Internal
val projectHelper = ProjectApiHelper.newInstance(project)
private val nodeExec: File
private val npmExec: File
init {
group = Vaadin.NODE_GROUP
description = "Download and install a local node/npm version."
onlyIf {
nodeExtension.download.get()
}
val nodeDirProvider = variantComputer.computeNodeDir(nodeExtension)
val npmDirProvider = variantComputer.computeNpmDir(nodeExtension, nodeDirProvider)
val nodeBinDirProvider = variantComputer.computeNodeBinDir(nodeDirProvider)
val npmBinDirProvider = variantComputer.computeNpmBinDir(npmDirProvider)
nodeExec = File(variantComputer.computeNodeExec(nodeExtension, nodeBinDirProvider).get()).absoluteFile
npmExec = File(variantComputer.computeNpmExec(nodeExtension, npmBinDirProvider).get()).absoluteFile
// only run node setup IF we do not already have node installed.
outputs.dir(nodeDirProvider)
}
@TaskAction
fun exec() {
// vaadin DEV MODE looks for node in the following location: basedir + "node/node_modules/npm/bin/npm-cli.js"
deleteExistingNode()
unpackNodeArchive()
renameDirectory()
setExecutableFlag()
validateInstall()
}
private fun deleteExistingNode() {
println("\tDeleting: ${nodeDir.get()}")
projectHelper.delete {
// only delete the nodejs dir, NOT the parent dir!
delete(nodeDir)
}
}
private fun unpackNodeArchive() {
val archiveFile = nodeArchiveFile.get().asFile
val nodeDirProvider = variantComputer.computeNodeDir(nodeExtension)
val nodeBinDirProvider = variantComputer.computeNodeBinDir(nodeDirProvider)
println("\t Unpacking: ${archiveFile}")
if (archiveFile.name.endsWith("zip")) {
projectHelper.copy {
from(projectHelper.zipTree(archiveFile))
into(nodeDirProvider.map { it.dir("../") })
}
} else {
projectHelper.copy {
from(projectHelper.tarTree(archiveFile))
into(nodeDirProvider.map { it.dir("../") })
}
// Fix broken symlink
val nodeBinDirPath = nodeBinDirProvider.get().asFile.toPath()
val npm = nodeBinDirPath.resolve("npm")
val npmScriptFile = variantComputer.computeNpmScriptFile(nodeDirProvider, "npm").get()
if (Files.deleteIfExists(npm)) {
Files.createSymbolicLink(npm, nodeBinDirPath.relativize(Paths.get(npmScriptFile)))
}
val npx = nodeBinDirPath.resolve("npx")
val npxScriptFile = variantComputer.computeNpmScriptFile(nodeDirProvider, "npx").get()
if (Files.deleteIfExists(npx)) {
Files.createSymbolicLink(npx, nodeBinDirPath.relativize(Paths.get(npxScriptFile)))
}
}
}
private fun renameDirectory() {
val nodeDirProvider = variantComputer.computeNodeDir(nodeExtension)
val extractionName = variantComputer.computeExtractionName(nodeExtension)
val nodeDir = nodeDirProvider.get().asFile
val baseFile = nodeDir.parentFile
val extractedNode = baseFile.resolve(extractionName)
val targetNode = nodeDir
println("\t Renaming: $extractedNode")
println("\t to: $targetNode")
extractedNode.renameTo(targetNode)
}
private fun setExecutableFlag() {
if (!PlatformHelper.INSTANCE.isWindows) {
val nodeDirProvider = variantComputer.computeNodeDir(nodeExtension)
val nodeBinDirProvider = variantComputer.computeNodeBinDir(nodeDirProvider)
val nodeExecProvider = variantComputer.computeNodeExec(nodeExtension, nodeBinDirProvider)
File(nodeExecProvider.get()).setExecutable(true)
}
}
private fun validateInstall() {
// gets the version of Node and NPM and compares them against the supported versions
var detectedVersion = Executor.run(nodeExec.absolutePath, "--version").let {
PlatformHelper.parseVersionString(it)
}
var parsedVersion = Version.from(detectedVersion)
println("\tNode info: $detectedVersion @ $nodeExec")
val SUPPORTED_NODE_VERSION = Version.from(Constants.SUPPORTED_NODE_MAJOR_VERSION, Constants.SUPPORTED_NODE_MINOR_VERSION
)
val SHOULD_WORK_NODE_VERSION = Version.from(Constants.SHOULD_WORK_NODE_MAJOR_VERSION, Constants.SHOULD_WORK_NODE_MINOR_VERSION
)
validateToolVersion("node", parsedVersion, SUPPORTED_NODE_VERSION, SHOULD_WORK_NODE_VERSION)
detectedVersion = Executor.run(npmExec.absolutePath, "--version").let {
PlatformHelper.parseVersionString(it)
}
parsedVersion = Version.from(detectedVersion)
println("\t NPM info: $detectedVersion @ $npmExec")
val SUPPORTED_NPM_VERSION = Version.from(Constants.SUPPORTED_NPM_MAJOR_VERSION, Constants.SUPPORTED_NPM_MINOR_VERSION
)
val SHOULD_WORK_NPM_VERSION = Version.from(Constants.SHOULD_WORK_NPM_MAJOR_VERSION, Constants.SHOULD_WORK_NPM_MINOR_VERSION
)
validateToolVersion("npm", parsedVersion, SUPPORTED_NPM_VERSION, SHOULD_WORK_NPM_VERSION)
}
companion object {
const val NAME = "nodeSetup"
}
}

View File

@ -0,0 +1,100 @@
package dorkbox.gradleVaadin.node.task
import dorkbox.gradleVaadin.NodeExtension
import dorkbox.gradleVaadin.Vaadin
import dorkbox.gradleVaadin.node.exec.NodeExecConfiguration
import dorkbox.gradleVaadin.node.exec.NodeExecRunner
import dorkbox.gradleVaadin.node.util.ProjectApiHelper
import org.gradle.api.Action
import org.gradle.api.DefaultTask
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.tasks.*
import org.gradle.api.tasks.PathSensitivity.RELATIVE
import org.gradle.kotlin.dsl.listProperty
import org.gradle.kotlin.dsl.mapProperty
import org.gradle.kotlin.dsl.property
import org.gradle.process.ExecSpec
import javax.inject.Inject
/**
* Gradle task for running a Node.js script
*/
abstract class NodeTask : DefaultTask() {
@get:Inject
abstract val objects: ObjectFactory
@get:Inject
abstract val providers: ProviderFactory
/**
* Node.js script to run.
*/
@get:InputFile
@get:PathSensitive(RELATIVE)
val script = objects.fileProperty()
/**
* Arguments to be passed to Node.js
*/
@get:Input
val options = objects.listProperty<String>()
/**
* Additional arguments for the [script] being run.
*/
@get:Input
val args = objects.listProperty<String>()
/**
* If enabled prevents the task from failing if the exit code is not 0. Defaults to false.
*/
@get:Input
val ignoreExitValue = objects.property<Boolean>().convention(false)
/**
* Sets the working directory.
*/
@get:Internal
val workingDir = objects.directoryProperty()
/**
* Add additional environment variables or override environment variables inherited from the system.
*/
@get:Input
val environment = objects.mapProperty<String, String>()
@get:Internal
val execOverrides = objects.property<Action<ExecSpec>>()
@get:Internal
val projectHelper = ProjectApiHelper.newInstance(project)
/**
* Overrides for [ExecSpec]
*/
@get:Internal
val extension = NodeExtension[project]
init {
group = Vaadin.NODE_GROUP
dependsOn(NodeSetupTask.NAME)
}
// For DSL
@Suppress("unused")
fun execOverrides(execOverrides: Action<ExecSpec>) {
this.execOverrides.set(execOverrides)
}
@TaskAction
fun exec() {
val currentScript = script.get().asFile
val command = options.get().plus(currentScript.absolutePath).plus(args.get())
val nodeExecConfiguration =
NodeExecConfiguration(command, environment.get(), workingDir.asFile.orNull,
ignoreExitValue.get(), execOverrides.orNull)
val nodeExecRunner = NodeExecRunner()
nodeExecRunner.execute(projectHelper, extension, nodeExecConfiguration)
}
}

View File

@ -0,0 +1,22 @@
package dorkbox.gradleVaadin.node.util
import java.util.concurrent.TimeUnit
/**
* Executes the given command and returns its output.
*
* This is based on an aggregate of the answers provided here: [https://stackoverflow.com/questions/35421699/how-to-invoke-external-command-from-within-kotlin-code]
*/
internal fun execute(vararg args: String, timeout: Long = 60): String {
return ProcessBuilder(args.toList())
.redirectOutput(ProcessBuilder.Redirect.PIPE)
.redirectError(ProcessBuilder.Redirect.PIPE)
.start()
.apply { waitFor(timeout, TimeUnit.SECONDS) }
.inputStream.bufferedReader().readText().trim()
}
/**
* Transforms the receiver with the given [transform] if the provided [predicate] evaluates to `true`. Otherwise, the receiver is returned.
*/
internal inline fun <T : Any?> T.mapIf(predicate: (T) -> Boolean, transform: (T) -> T): T = if (predicate(this)) transform(this) else this

View File

@ -0,0 +1,111 @@
package dorkbox.gradleVaadin.node.util
import com.dorkbox.version.Version
import com.vaadin.flow.server.frontend.FrontendUtils
import com.vaadin.flow.server.frontend.FrontendVersion
import java.io.IOException
import java.util.*
import java.util.stream.Stream
open class PlatformHelper constructor(private val props: Properties = System.getProperties()) {
open val osName: String by lazy {
val name = property("os.name").toLowerCase()
when {
name.contains("windows") -> "win"
name.contains("mac") -> "darwin"
name.contains("linux") -> "linux"
name.contains("freebsd") -> "linux"
name.contains("sunos") -> "sunos"
else -> error("Unsupported OS: $name")
}
}
open val osArch: String by lazy {
val arch = property("os.arch").toLowerCase()
when {
/*
* As Java just returns "arm" on all ARM variants, we need a system call to determine the exact arch. Unfortunately some JVMs say aarch32/64, so we need an additional
* conditional. Additionally, the node binaries for 'armv8l' are called 'arm64', so we need to distinguish here.
*/
arch == "arm" || arch.startsWith("aarch") -> property("uname")
.mapIf({ it == "armv8l" || it == "aarch64" }) { "arm64" }
arch == "ppc64le" -> "ppc64le"
arch.contains("64") -> "x64"
else -> "x86"
}
}
open val isWindows: Boolean by lazy { osName == "win" }
private fun property(name: String): String {
val value = props.getProperty(name)
return value ?: System.getProperty(name) ?:
// Added so that we can test osArch on Windows and on non-arm systems
if (name == "uname") execute("uname", "-m")
else error("Unable to find a value for property [$name].")
}
companion object {
var INSTANCE = PlatformHelper()
@Throws(IOException::class)
fun parseVersionString(output: String): String {
val regex = "\n".toRegex()
val regex1 = "^[ ]*$".toRegex()
val lastOuput = Stream.of(*output.split(regex).toTypedArray())
.filter { line: String -> !line.matches(regex1) }
.reduce { first: String, second: String -> second }
return lastOuput
.map { line: String ->
line.replaceFirst("^v".toRegex(), "").trim()
}
.orElseThrow { IOException("No output") }
}
fun validateToolVersion(
tool: String, toolVersion: Version,
supported: Version, shouldWork: Version
) {
if ("true".equals(System.getProperty(FrontendUtils.PARAM_IGNORE_VERSION_CHECKS), ignoreCase = true)) {
return
}
if (toolVersion.greaterThanOrEqualTo(supported)) {
return
}
throw IllegalStateException(
buildTooOldString(
tool, toolVersion.toString(),
supported.majorVersion.toInt(),
supported.minorVersion.toInt()
)
)
}
fun isVersionAtLeast(toolVersion: FrontendVersion, required: FrontendVersion): Boolean {
val major = toolVersion.majorVersion
val minor = toolVersion.minorVersion
return (major > required.majorVersion
|| major == required.majorVersion && minor >= required.minorVersion)
}
private fun buildTooOldString(tool: String, version: String,supportedMajor: Int, supportedMinor: Int): String? {
return String.format(
TOO_OLD, tool, version, supportedMajor, supportedMinor,
FrontendUtils.PARAM_IGNORE_VERSION_CHECKS
)
}
private const val TOO_OLD =
("%n%n======================================================================================================"
+ "%nYour installed '%s' version (%s) is too old. Supported versions are %d.%d+" //
+ "%nPlease install a new one either:"
+ "%n - by following the https://nodejs.org/en/download/ guide to install it globally"
+ "%n - or by running the frontend-maven-plugin goal to install it in this project:"
+ FrontendUtils.INSTALL_NODE_LOCALLY
+ "%n" //
+ FrontendUtils.DISABLE_CHECK //
+ "%n======================================================================================================%n")
}
}

View File

@ -0,0 +1,102 @@
@file:Suppress("UnstableApiUsage")
package dorkbox.gradleVaadin.node.util
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.file.*
import org.gradle.api.model.ObjectFactory
import org.gradle.api.tasks.WorkResult
import org.gradle.process.ExecOperations
import org.gradle.process.ExecResult
import org.gradle.process.ExecSpec
import org.gradle.util.GradleVersion
import java.io.File
import javax.inject.Inject
interface ProjectApiHelper {
companion object {
@JvmStatic
fun newInstance(project: Project): ProjectApiHelper {
return if (enableConfigurationCache()) {
project.objects.newInstance(DefaultProjectApiHelper::class.java)
} else {
LegacyProjectApiHelper(project)
}
}
private fun enableConfigurationCache(): Boolean {
return GradleVersion.current() >= GradleVersion.version("6.6")
}
}
fun fileTree(directory: Directory): ConfigurableFileTree
fun zipTree(tarPath: File): FileTree
fun tarTree(tarPath: File): FileTree
fun copy(action: Action<CopySpec>): WorkResult
fun delete(action: Action<DeleteSpec>): WorkResult
fun exec(action: Action<ExecSpec>): ExecResult
}
internal open class DefaultProjectApiHelper @Inject constructor(
private val factory: ObjectFactory,
private val execOperations: ExecOperations,
private val fileSystemOperations: FileSystemOperations,
private val archiveOperations: ArchiveOperations) : ProjectApiHelper {
override fun fileTree(directory: Directory): ConfigurableFileTree {
return factory.fileTree().from(directory)
}
override fun zipTree(tarPath: File): FileTree {
return archiveOperations.zipTree(tarPath)
}
override fun tarTree(tarPath: File): FileTree {
return archiveOperations.tarTree(tarPath)
}
override fun copy(action: Action<CopySpec>): WorkResult {
return fileSystemOperations.copy(action)
}
override fun delete(action: Action<DeleteSpec>): WorkResult {
return fileSystemOperations.delete(action)
}
override fun exec(action: Action<ExecSpec>): ExecResult {
return execOperations.exec(action)
}
}
internal open class LegacyProjectApiHelper(private val project: Project) : ProjectApiHelper {
override fun fileTree(directory: Directory): ConfigurableFileTree {
return project.fileTree(directory)
}
override fun zipTree(tarPath: File): FileTree {
return project.zipTree(tarPath)
}
override fun tarTree(tarPath: File): FileTree {
return project.tarTree(tarPath)
}
override fun copy(action: Action<CopySpec>): WorkResult {
return project.copy(action)
}
override fun delete(action: Action<DeleteSpec>): WorkResult {
return project.delete(action)
}
override fun exec(action: Action<ExecSpec>): ExecResult {
return project.exec(action)
}
}

View File

@ -0,0 +1,42 @@
package dorkbox.gradleVaadin.node.util
import org.gradle.api.provider.Provider
internal fun <A, B> zip(aProvider: Provider<A>, bProvider: Provider<B>): Provider<Pair<A, B>> {
return aProvider.flatMap { a -> bProvider.map { b -> Pair(a!!, b!!) } }
}
internal fun <A, B, C> zip(aProvider: Provider<A>, bProvider: Provider<B>, cProvider: Provider<C>):
Provider<Triple<A, B, C>> {
return zip(aProvider, bProvider).flatMap { pair -> cProvider.map { c -> Triple(pair.first, pair.second, c!!) } }
}
internal fun <A, B, C, D> zip(aProvider: Provider<A>, bProvider: Provider<B>, cProvider: Provider<C>,
dProvider: Provider<D>): Provider<Tuple4<A, B, C, D>> {
return zip(zip(aProvider, bProvider), zip(cProvider, dProvider))
.map { pairs -> Tuple4(pairs.first.first, pairs.first.second, pairs.second.first, pairs.second.second) }
}
internal data class Tuple4<A, B, C, D>(
val first: A,
val second: B,
val third: C,
val fourth: D
)
internal fun <A, B, C, D, E> zip(aProvider: Provider<A>, bProvider: Provider<B>, cProvider: Provider<C>,
dProvider: Provider<D>, eProvider: Provider<E>): Provider<Tuple5<A, B, C, D, E>> {
return zip(zip(aProvider, bProvider), zip(cProvider, dProvider, eProvider))
.map { pairs ->
Tuple5(pairs.first.first, pairs.first.second, pairs.second.first, pairs.second.second,
pairs.second.third)
}
}
internal data class Tuple5<A, B, C, D, E>(
val first: A,
val second: B,
val third: C,
val fourth: D,
val fifth: E
)

View File

@ -0,0 +1,115 @@
package dorkbox.gradleVaadin.node.variant
import dorkbox.gradleVaadin.NodeExtension
import dorkbox.gradleVaadin.node.util.PlatformHelper
import dorkbox.gradleVaadin.node.util.mapIf
import dorkbox.gradleVaadin.node.util.zip
import org.gradle.api.file.Directory
import org.gradle.api.provider.Provider
internal class VariantComputer @JvmOverloads constructor(
private val platformHelper: PlatformHelper = PlatformHelper.INSTANCE
) {
fun computeExtractionName(nodeExtension: NodeExtension): String {
val osName = platformHelper.osName
val osArch = platformHelper.osArch
return "node-v${nodeExtension.version.get()}-$osName-$osArch"
}
fun computeNodeDir(nodeExtension: NodeExtension): Provider<Directory> {
return zip(nodeExtension.workDir, nodeExtension.version).map { (workDir, _) ->
workDir
}
}
fun computeNodeBinDir(nodeDirProvider: Provider<Directory>) = computeProductBinDir(nodeDirProvider)
fun computeNodeExec(nodeExtension: NodeExtension, nodeBinDirProvider: Provider<Directory>): Provider<String> {
return zip(nodeExtension.download, nodeBinDirProvider).map {
val (download, nodeBinDir) = it
if (download) {
val nodeCommand = if (platformHelper.isWindows) "node.exe" else "node"
nodeBinDir.dir(nodeCommand).asFile.absolutePath
} else "node"
}
}
fun computeNpmDir(nodeExtension: NodeExtension, nodeDirProvider: Provider<Directory>): Provider<Directory> {
return zip(nodeExtension.npmVersion, nodeExtension.npmWorkDir, nodeDirProvider).map {
val (npmVersion, npmWorkDir, nodeDir) = it
if (npmVersion.isNotBlank()) {
val directoryName = "npm-v${npmVersion}"
npmWorkDir.dir(directoryName)
} else nodeDir
}
}
fun computeNpmBinDir(npmDirProvider: Provider<Directory>) = computeProductBinDir(npmDirProvider)
fun computeNpmExec(nodeExtension: NodeExtension, npmBinDirProvider: Provider<Directory>): Provider<String> {
return zip(nodeExtension.download, nodeExtension.npmCommand, npmBinDirProvider).map {
val (download, npmCommand, npmBinDir) = it
val command = if (platformHelper.isWindows) {
npmCommand.mapIf({ it == "npm" }) { "npm.cmd" }
} else npmCommand
if (download) npmBinDir.dir(command).asFile.absolutePath else command
}
}
fun computeNpmScriptFile(nodeDirProvider: Provider<Directory>, command: String): Provider<String> {
return nodeDirProvider.map { nodeDir ->
if (platformHelper.isWindows) nodeDir.dir("node_modules/npm/bin/$command-cli.js").asFile.path
else nodeDir.dir("lib/node_modules/npm/bin/$command-cli.js").asFile.path
}
}
fun computeNodeModulesDir(nodeExtension: NodeExtension): Provider<Directory> {
val nodeDirProvider = computeNodeDir(nodeExtension)
return nodeDirProvider.map { nodeDir ->
nodeDir.dir("node_modules")
}
}
fun computeNpxExec(nodeExtension: NodeExtension, npmBinDirProvider: Provider<Directory>): Provider<String> {
return zip(nodeExtension.download, nodeExtension.npxCommand, npmBinDirProvider).map {
val (download, npxCommand, npmBinDir) = it
val command = if (platformHelper.isWindows) {
npxCommand.mapIf({ it == "npx" }) { "npx.cmd" }
} else npxCommand
if (download) npmBinDir.dir(command).asFile.absolutePath else command
}
}
fun computeYarnDir(nodeExtension: NodeExtension): Provider<Directory> {
return zip(nodeExtension.yarnVersion, nodeExtension.yarnWorkDir).map {
val (yarnVersion, yarnWorkDir) = it
val dirnameSuffix = if (yarnVersion.isNotBlank()) {
"-v${yarnVersion}"
} else "-latest"
val dirname = "yarn$dirnameSuffix"
yarnWorkDir.dir(dirname)
}
}
fun computeYarnBinDir(yarnDirProvider: Provider<Directory>) = computeProductBinDir(yarnDirProvider)
fun computeYarnExec(nodeExtension: NodeExtension, yarnBinDirProvider: Provider<Directory>): Provider<String> {
return zip(nodeExtension.yarnCommand, nodeExtension.download, yarnBinDirProvider).map {
val (yarnCommand, download, yarnBinDir) = it
val command = if (platformHelper.isWindows) {
yarnCommand.mapIf({ it == "yarn" }) { "yarn.cmd" }
} else yarnCommand
if (download) yarnBinDir.dir(command).asFile.absolutePath else command
}
}
private fun computeProductBinDir(productDirProvider: Provider<Directory>) =
if (platformHelper.isWindows) productDirProvider else productDirProvider.map { it.dir("bin") }
fun computeNodeArchiveDependency(nodeExtension: NodeExtension): Provider<String> {
val osName = platformHelper.osName
val osArch = platformHelper.osArch
val type = if (platformHelper.isWindows) "zip" else "tar.gz"
return nodeExtension.version.map { version -> "org.nodejs:node:$version:$osName-$osArch@$type" }
}
}

View File

@ -0,0 +1,65 @@
package dorkbox.gradleVaadin.node.yarn.exec
import dorkbox.gradleVaadin.NodeExtension
import dorkbox.gradleVaadin.node.exec.ExecConfiguration
import dorkbox.gradleVaadin.node.exec.ExecRunner
import dorkbox.gradleVaadin.node.exec.NodeExecConfiguration
import dorkbox.gradleVaadin.node.npm.proxy.NpmProxy
import dorkbox.gradleVaadin.node.util.ProjectApiHelper
import dorkbox.gradleVaadin.node.util.zip
import dorkbox.gradleVaadin.node.variant.VariantComputer
import org.gradle.api.file.Directory
import org.gradle.api.provider.Provider
import org.gradle.api.provider.ProviderFactory
import javax.inject.Inject
internal abstract class YarnExecRunner {
@get:Inject
abstract val providers: ProviderFactory
private val variantComputer = VariantComputer()
fun executeYarnCommand(project: ProjectApiHelper, nodeExtension: NodeExtension, nodeExecConfiguration: NodeExecConfiguration) {
val nodeDirProvider = variantComputer.computeNodeDir(nodeExtension)
val yarnDirProvider = variantComputer.computeYarnDir(nodeExtension)
val yarnBinDirProvider = variantComputer.computeYarnBinDir(yarnDirProvider)
val yarnExecProvider = variantComputer.computeYarnExec(nodeExtension, yarnBinDirProvider)
val additionalBinPathProvider =
computeAdditionalBinPath(nodeExtension, nodeDirProvider, yarnBinDirProvider)
val execConfiguration = ExecConfiguration(yarnExecProvider.get(),
nodeExecConfiguration.command, additionalBinPathProvider.get(),
addNpmProxyEnvironment(nodeExtension, nodeExecConfiguration), nodeExecConfiguration.workingDir,
nodeExecConfiguration.ignoreExitValue, nodeExecConfiguration.execOverrides)
val execRunner = ExecRunner()
execRunner.execute(project, nodeExtension, execConfiguration)
}
private fun addNpmProxyEnvironment(nodeExtension: NodeExtension,
nodeExecConfiguration: NodeExecConfiguration
): Map<String, String> {
if (NpmProxy.shouldConfigureProxy(System.getenv(), nodeExtension.nodeProxySettings.get())) {
val npmProxyEnvironmentVariables = NpmProxy.computeNpmProxyEnvironmentVariables()
if (npmProxyEnvironmentVariables.isNotEmpty()) {
return nodeExecConfiguration.environment.plus(npmProxyEnvironmentVariables)
}
}
return nodeExecConfiguration.environment
}
private fun computeAdditionalBinPath(nodeExtension: NodeExtension,
nodeDirProvider: Provider<Directory>,
yarnBinDirProvider: Provider<Directory>): Provider<List<String>> {
return nodeExtension.download.flatMap { download ->
if (!download) {
providers.provider { listOf<String>() }
}
val nodeBinDirProvider = variantComputer.computeNodeBinDir(nodeDirProvider)
val npmDirProvider = variantComputer.computeNpmDir(nodeExtension, nodeDirProvider)
val npmBinDirProvider = variantComputer.computeNpmBinDir(npmDirProvider)
zip(nodeBinDirProvider, npmBinDirProvider, yarnBinDirProvider)
.map { (nodeBinDir, npmBinDir, yarnBinDir) ->
listOf(yarnBinDir, npmBinDir, nodeBinDir).map { file -> file.asFile.absolutePath }
}
}
}
}

View File

@ -0,0 +1,87 @@
package dorkbox.gradleVaadin.node.yarn.task
import dorkbox.gradleVaadin.Vaadin
import dorkbox.gradleVaadin.node.util.zip
import org.gradle.api.Action
import org.gradle.api.file.ConfigurableFileTree
import org.gradle.api.file.Directory
import org.gradle.api.file.FileTree
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.*
import org.gradle.api.tasks.PathSensitivity.RELATIVE
import org.gradle.kotlin.dsl.property
import java.io.File
/**
* yarn install that only gets executed if gradle decides so.
*/
abstract class YarnInstallTask : YarnTask() {
@get:Internal
val nodeModulesOutputFilter = objects.property<Action<ConfigurableFileTree>>()
init {
group = Vaadin.YARN_GROUP
description = "Install node packages using Yarn."
dependsOn(YarnSetupTask.NAME)
}
@PathSensitive(RELATIVE)
@Optional
@InputFile
protected fun getPackageJsonFile(): Provider<File> {
return projectFileIfExists("package.json")
}
@PathSensitive(RELATIVE)
@Optional
@InputFile
protected fun getYarnLockFile(): Provider<File> {
return projectFileIfExists("yarn.lock")
}
@Optional
@OutputFile
protected fun getYarnLockFileAsOutput(): Provider<File> {
return projectFileIfExists("yarn.lock")
}
private fun projectFileIfExists(name: String): Provider<File> {
return nodeExtension.nodeProjectDir.map { it.file(name).asFile }
.flatMap { if (it.exists()) providers.provider { it } else providers.provider { null } }
}
@Optional
@OutputDirectory
@Suppress("unused")
protected fun getNodeModulesDirectory(): Provider<Directory> {
val filter = nodeModulesOutputFilter.orNull
return if (filter == null) nodeExtension.nodeProjectDir.dir("node_modules")
else providers.provider { null }
}
@Optional
@OutputFiles
@Suppress("unused")
protected fun getNodeModulesFiles(): Provider<FileTree> {
val nodeModulesDirectoryProvider = nodeExtension.nodeProjectDir.dir("node_modules")
return zip(nodeModulesDirectoryProvider, nodeModulesOutputFilter)
.flatMap { (nodeModulesDirectory, nodeModulesOutputFilter) ->
if (nodeModulesOutputFilter != null) {
val fileTree = projectHelper.fileTree(nodeModulesDirectory)
nodeModulesOutputFilter.execute(fileTree)
providers.provider { fileTree }
} else providers.provider { null }
}
}
// For DSL
@Suppress("unused")
fun nodeModulesOutputFilter(nodeModulesOutputFilter: Action<ConfigurableFileTree>) {
this.nodeModulesOutputFilter.set(nodeModulesOutputFilter)
}
companion object {
const val NAME = "yarn"
}
}

View File

@ -0,0 +1,49 @@
package dorkbox.gradleVaadin.node.yarn.task
import dorkbox.gradleVaadin.Vaadin
import dorkbox.gradleVaadin.node.npm.task.NpmSetupTask
import dorkbox.gradleVaadin.node.variant.VariantComputer
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.OutputDirectory
import java.io.File
/**
* Setup a specific version of Yarn to be used by the build.
*/
abstract class YarnSetupTask : NpmSetupTask() {
init {
group = Vaadin.YARN_GROUP
description = "Setup a specific version of Yarn to be used by the build."
}
@Input
override fun getVersion(): Provider<String> {
return nodeExtension.yarnVersion
}
@get:OutputDirectory
val yarnDir by lazy {
val variantComputer = VariantComputer()
variantComputer.computeYarnDir(nodeExtension)
}
override fun computeCommand(): List<String> {
val version = nodeExtension.yarnVersion.get()
val yarnDir = yarnDir.get()
val yarnPackage = if (version.isNotBlank()) "yarn@$version" else "yarn"
// npm < 7 creates the directory if it's missing, >= 7 fails if it's missing
// create the directory since we use npm to install yarn.
File(yarnDir.asFile, "lib").mkdirs()
return listOf("install", "--global", "--no-save", "--prefix", yarnDir.asFile.absolutePath, yarnPackage)
.plus(args.get())
}
override fun isTaskEnabled(): Boolean {
return true
}
companion object {
const val NAME = "yarnSetup"
}
}

View File

@ -0,0 +1,76 @@
package dorkbox.gradleVaadin.node.yarn.task
import dorkbox.gradleVaadin.NodeExtension
import dorkbox.gradleVaadin.Vaadin
import dorkbox.gradleVaadin.node.exec.NodeExecConfiguration
import dorkbox.gradleVaadin.node.util.ProjectApiHelper
import dorkbox.gradleVaadin.node.yarn.exec.YarnExecRunner
import org.gradle.api.Action
import org.gradle.api.DefaultTask
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.TaskAction
import org.gradle.kotlin.dsl.listProperty
import org.gradle.kotlin.dsl.mapProperty
import org.gradle.kotlin.dsl.property
import org.gradle.process.ExecSpec
import javax.inject.Inject
abstract class YarnTask : DefaultTask() {
@get:Inject
abstract val objects: ObjectFactory
@get:Inject
abstract val providers: ProviderFactory
@get:Optional
@get:Input
val yarnCommand = objects.listProperty<String>()
@get:Optional
@get:Input
val args = objects.listProperty<String>()
@get:Input
val ignoreExitValue = objects.property<Boolean>().convention(false)
@get:Internal
val workingDir = objects.directoryProperty()
@get:Input
val environment = objects.mapProperty<String, String>()
@get:Internal
val execOverrides = objects.property<Action<ExecSpec>>()
@get:Internal
val projectHelper = ProjectApiHelper.newInstance(project)
@get:Internal
val nodeExtension = NodeExtension[project]
init {
group = Vaadin.NODE_GROUP
dependsOn(YarnSetupTask.NAME)
}
// For DSL
@Suppress("unused")
fun execOverrides(execOverrides: Action<ExecSpec>) {
this.execOverrides.set(execOverrides)
}
@TaskAction
fun exec() {
val command = yarnCommand.get().plus(args.get())
val nodeExecConfiguration =
NodeExecConfiguration(command, environment.get(), workingDir.asFile.orNull,
ignoreExitValue.get(), execOverrides.orNull)
val yarnExecRunner = objects.newInstance(YarnExecRunner::class.java)
yarnExecRunner.executeYarnCommand(projectHelper, nodeExtension, nodeExecConfiguration)
}
}