├── .github
└── workflows
│ └── test.yaml
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── build.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── scripts
├── publish.sh
└── verify.sh
├── settings.gradle
└── src
├── main
└── groovy
│ └── com
│ └── jetbrains
│ └── python
│ └── envs
│ ├── PythonEnvsExtension.groovy
│ └── PythonEnvsPlugin.groovy
└── test
└── groovy
└── com
└── jetbrains
└── python
└── envs
└── test
└── FunctionalTest.groovy
/.github/workflows/test.yaml:
--------------------------------------------------------------------------------
1 | name: test
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | jobs:
9 | build-plugin:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - name: Checkout source code
14 | uses: actions/checkout@v4
15 |
16 | - name: Set up JDK 8
17 | uses: actions/setup-java@v4
18 | with:
19 | distribution: corretto
20 | java-version: 8
21 |
22 | - name: Setup Gradle
23 | uses: gradle/actions/setup-gradle@v3
24 |
25 | - name: Test
26 | run: ./gradlew test
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | /.gradle/
3 | /.idea/
4 | /build/
5 | *.ipr
6 | *.iws
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: groovy
2 |
3 | os:
4 | - linux
5 |
6 | jdk:
7 | - openjdk8
8 |
9 | dist: xenial
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright 2014 Palantir Technologies
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
2 |
3 | Gradle Python Envs Plugin
4 | ========================
5 |
6 | Gradle plugin to create Python envs.
7 |
8 | This plugin is based on [gradle-miniconda-plugin](https://github.com/palantir/gradle-miniconda-plugin),
9 | but in addition to creating Conda envs it provides:
10 |
11 | 1. A convenient DSL to specify target Python environments
12 | 2. Creating Python envs for Unix with [python-build](https://github.com/pyenv/pyenv/tree/master/plugins/python-build) for Unix
13 |
N.B.: [Common build problems](https://github.com/pyenv/pyenv/wiki/Common-build-problems) article from pyenv
14 | 3. Creating Python envs for Windows by installing msi or exe from [python.org](https://www.python.org/).
15 |
N.B.: Windows UAC should be switched off, otherwise - use Python from zip
16 | 4. Both Anaconda and Miniconda support (32 and 64 bit versions)
17 | 5. Creating Conda envrionments, conda package installation support
18 | 6. Creating Jython environments
19 | 7. Creating PyPy environments (only Unix is supported, by default pypy2.7-5.8.0 version is used)
20 | 8. Creating IronPython environments (only Windows is supported, by default [2.7.9 version](https://github.com/IronLanguages/ironpython2/releases/tag/ipy-2.7.9) is used)
21 | 9. Virtualenv creation from any python environment created
22 | 10. Python from zip creation: downloading archive from specified url, unpacking and preparing to work with
23 | 11. Package installation for any environment or virtualenv with specified install options
24 |
25 |
26 | Usage
27 | -----
28 |
29 | Apply the plugin to your project following
30 | [`https://plugins.gradle.org/plugin/com.jetbrains.python.envs`](https://plugins.gradle.org/plugin/com.jetbrains.python.envs),
31 | and configure the associated extension:
32 |
33 | ```gradle
34 | envs {
35 | bootstrapDirectory = new File(buildDir, 'bootstrap')
36 | envsDirectory = new File(buildDir, 'envs')
37 |
38 | // Download python zips when Windows is used from https://repository.net/%archieveName%,
39 | // where {archieveName} is python-{version}-{architecture}.zip.
40 | // For example, for the 64 bit version of Python 3.7.2 the archive name will be python-3.7.2-64.zip
41 | zipRepository = new URL("https://repository.net/")
42 | shouldUseZipsFromRepository = Os.isFamily(Os.FAMILY_WINDOWS)
43 |
44 | // by default if architecture isn't specified - 64 bit one is used
45 | // _64Bits = true
46 |
47 | // by default pipInstallOptions equals to "--trusted-host pypi.python.org"
48 | // to fix CERTIFICATE_VERIFY_FAILED ssl error
49 | // pipInstallOptions = "--trusted-host pypi.python.org"
50 |
51 | //python "envName", "version", []
52 | python "python35_64", "3.5.3", ["django==1.9"]
53 | //python "envName", "version", "architecture", []
54 | python "python36_32", "3.6.2", "32", ["django==1.10"]
55 | //python "envName", "version", "architecture", [], patchUri
56 | python "python310_64", "3.10.0", "64", [], "file://path/to/a.patch"
57 | //virtualenv "virtualEnvName", "sourceEnvName", []
58 | virtualenv "envPython35", "python35_64", ["pytest"]
59 | virtualenv "envPython36", "python36_32", ["behave", "requests"]
60 |
61 | //conda "envName", "version", "architecture"
62 | conda "Miniconda3", "Miniconda3-latest", "64"
63 | //conda "envName", "version", []
64 | conda "Anaconda2", "Anaconda2-4.4.0", [condaPackage("PyQt")]
65 | //conda "envName", "version", "architecture", []
66 | conda "Anaconda3", "Anaconda3-4.4.0", "64", ["django==1.8"]
67 |
68 | //condaenv "envName", "version", []
69 | // Here will be created additional "Miniconda2-latest"
70 | // (or another one specified in condaDefaultVersion value)
71 | // conda interpreter to be bootstraped
72 | condaenv "pyqt_env", "2.7", [condaPackage("pyqt")]
73 | //condaenv "envName", "version", "sourceEnvName", []
74 | condaenv "django19", "2.7", "Miniconda3", ["django==1.9"]
75 | condaenv "conda34", "3.4", "Miniconda3", ["ipython==2.1", "django==1.6", "behave", "jinja2", "tox==2.0"]
76 |
77 | if (Os.isFamily(Os.FAMILY_WINDOWS)) {
78 | // This links are used for envs like tox; *nix envs have such links already
79 | link "bin/python2.7.exe", "bin/python.exe", new File(envsDirectory, "django19")
80 | link "bin/python3.4.exe", "bin/python.exe", new File(envsDirectory, "conda34")
81 | }
82 |
83 | //jython "envName", []
84 | jython "jython"
85 | virtualenv "envJython", "jython", ["django==1.8"]
86 |
87 | if (Os.isFamily(Os.FAMILY_UNIX)) {
88 | //pypy "envName", []
89 | pypy "pypy2", ["django"]
90 | virtualenv "envPypy2", "pypy2", ["pytest"]
91 | //pypy "envName", "version", []
92 | //version should be in accordance with python-build
93 | pypy "pypy3", "pypy3.5-5.8.0", ["nose"]
94 | virtualenv "envPypy3", "pypy3", ["django"]
95 | }
96 |
97 | if (Os.isFamily(Os.FAMILY_WINDOWS)) {
98 | //ironpython "envName", []
99 | ironpython "ironpython64", ["requests"]
100 | //ironpython "envName", "architecture", []
101 | ironpython "ironpython32", "32", ["requests"]
102 | // ironpython doesn't support virtualenvs at all
103 | }
104 | }
105 | ```
106 |
107 | Then invoke the `build_envs` task.
108 |
109 | This will download and install specified python's interpreters (python, anaconda and miniconda, jython, pypy, ironpython) to `buildDir/bootstrap`.
110 |
111 | Then it will create several conda and virtual envs in `buildDir/envs`.
112 |
113 | Libraries listed will be installed correspondingly. Packages in list are installed with `pip install` command. If the function `condaPackage()` was called for package name, it will be installed with `conda install` command. It enables to install, for example, PyQt in env.
114 |
115 |
116 | License
117 | -------
118 |
119 | The code is licensed under the Apache 2.0 License. See the included
120 | [LICENSE](LICENSE) file for details.
121 |
122 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'idea'
3 | id 'groovy'
4 | id 'com.gradle.plugin-publish' version '1.2.1'
5 | id 'com.palantir.git-version' version '3.1.0'
6 | id 'maven-publish'
7 | id 'java-gradle-plugin'
8 | }
9 |
10 | project.version = gitVersion()
11 | project.group = 'com.jetbrains.python'
12 |
13 | java {
14 | sourceCompatibility = JavaVersion.VERSION_21
15 | }
16 |
17 | repositories {
18 | mavenCentral()
19 | }
20 |
21 | publishing {
22 | repositories {
23 | maven {
24 | url uri('../mvn_repo')
25 | }
26 | }
27 | }
28 |
29 | dependencies {
30 | implementation gradleApi()
31 |
32 | testImplementation(
33 | gradleTestKit(),
34 | )
35 |
36 | testImplementation(platform('org.junit:junit-bom:5.10.3'))
37 | testImplementation('org.junit.jupiter:junit-jupiter')
38 | testRuntimeOnly('org.junit.platform:junit-platform-launcher')
39 |
40 | testImplementation('org.spockframework:spock-core:2.4-M4-groovy-3.0') {
41 | exclude module: 'groovy-all'
42 | }
43 | }
44 |
45 | gradlePlugin {
46 | website = 'https://github.com/JetBrains/gradle-python-envs'
47 | vcsUrl = 'https://github.com/JetBrains/gradle-python-envs'
48 | plugins {
49 | pythonEnvsPlugin {
50 | id = 'com.jetbrains.python.envs'
51 | implementationClass = 'com.jetbrains.python.envs.PythonEnvsPlugin'
52 | displayName = 'Gradle Python Envs plugin'
53 | description = 'Gradle plugin to install different Python environments'
54 | tags.set(['python', 'miniconda', 'conda'])
55 | }
56 | }
57 | }
58 |
59 | test {
60 | useJUnitPlatform()
61 | testLogging {
62 | showCauses = true
63 | showExceptions = true
64 | showStackTraces = true
65 | showStandardStreams = true
66 |
67 | events "started", "passed", "skipped", "failed", "standard_out", "standard_error"
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JetBrains/gradle-python-envs/8f0c2be6752e1b9abcdbf079be9d4f32e5317121/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | #
21 | # Gradle start up script for POSIX generated by Gradle.
22 | #
23 | # Important for running:
24 | #
25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
26 | # noncompliant, but you have some other compliant shell such as ksh or
27 | # bash, then to run this script, type that shell name before the whole
28 | # command line, like:
29 | #
30 | # ksh Gradle
31 | #
32 | # Busybox and similar reduced shells will NOT work, because this script
33 | # requires all of these POSIX shell features:
34 | # * functions;
35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
37 | # * compound commands having a testable exit status, especially «case»;
38 | # * various built-in commands including «command», «set», and «ulimit».
39 | #
40 | # Important for patching:
41 | #
42 | # (2) This script targets any POSIX shell, so it avoids extensions provided
43 | # by Bash, Ksh, etc; in particular arrays are avoided.
44 | #
45 | # The "traditional" practice of packing multiple parameters into a
46 | # space-separated string is a well documented source of bugs and security
47 | # problems, so this is (mostly) avoided, by progressively accumulating
48 | # options in "$@", and eventually passing that to Java.
49 | #
50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
52 | # see the in-line comments for details.
53 | #
54 | # There are tweaks for specific operating systems such as AIX, CygWin,
55 | # Darwin, MinGW, and NonStop.
56 | #
57 | # (3) This script is generated from the Groovy template
58 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
59 | # within the Gradle project.
60 | #
61 | # You can find Gradle at https://github.com/gradle/gradle/.
62 | #
63 | ##############################################################################
64 |
65 | # Attempt to set APP_HOME
66 |
67 | # Resolve links: $0 may be a link
68 | app_path=$0
69 |
70 | # Need this for daisy-chained symlinks.
71 | while
72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
73 | [ -h "$app_path" ]
74 | do
75 | ls=$( ls -ld "$app_path" )
76 | link=${ls#*' -> '}
77 | case $link in #(
78 | /*) app_path=$link ;; #(
79 | *) app_path=$APP_HOME$link ;;
80 | esac
81 | done
82 |
83 | # This is normally unused
84 | # shellcheck disable=SC2034
85 | APP_BASE_NAME=${0##*/}
86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
88 |
89 | # Use the maximum available, or set MAX_FD != -1 to use that value.
90 | MAX_FD=maximum
91 |
92 | warn () {
93 | echo "$*"
94 | } >&2
95 |
96 | die () {
97 | echo
98 | echo "$*"
99 | echo
100 | exit 1
101 | } >&2
102 |
103 | # OS specific support (must be 'true' or 'false').
104 | cygwin=false
105 | msys=false
106 | darwin=false
107 | nonstop=false
108 | case "$( uname )" in #(
109 | CYGWIN* ) cygwin=true ;; #(
110 | Darwin* ) darwin=true ;; #(
111 | MSYS* | MINGW* ) msys=true ;; #(
112 | NONSTOP* ) nonstop=true ;;
113 | esac
114 |
115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
116 |
117 |
118 | # Determine the Java command to use to start the JVM.
119 | if [ -n "$JAVA_HOME" ] ; then
120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
121 | # IBM's JDK on AIX uses strange locations for the executables
122 | JAVACMD=$JAVA_HOME/jre/sh/java
123 | else
124 | JAVACMD=$JAVA_HOME/bin/java
125 | fi
126 | if [ ! -x "$JAVACMD" ] ; then
127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
128 |
129 | Please set the JAVA_HOME variable in your environment to match the
130 | location of your Java installation."
131 | fi
132 | else
133 | JAVACMD=java
134 | if ! command -v java >/dev/null 2>&1
135 | then
136 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
137 |
138 | Please set the JAVA_HOME variable in your environment to match the
139 | location of your Java installation."
140 | fi
141 | fi
142 |
143 | # Increase the maximum file descriptors if we can.
144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
145 | case $MAX_FD in #(
146 | max*)
147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
148 | # shellcheck disable=SC2039,SC3045
149 | MAX_FD=$( ulimit -H -n ) ||
150 | warn "Could not query maximum file descriptor limit"
151 | esac
152 | case $MAX_FD in #(
153 | '' | soft) :;; #(
154 | *)
155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
156 | # shellcheck disable=SC2039,SC3045
157 | ulimit -n "$MAX_FD" ||
158 | warn "Could not set maximum file descriptor limit to $MAX_FD"
159 | esac
160 | fi
161 |
162 | # Collect all arguments for the java command, stacking in reverse order:
163 | # * args from the command line
164 | # * the main class name
165 | # * -classpath
166 | # * -D...appname settings
167 | # * --module-path (only if needed)
168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
169 |
170 | # For Cygwin or MSYS, switch paths to Windows format before running java
171 | if "$cygwin" || "$msys" ; then
172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
174 |
175 | JAVACMD=$( cygpath --unix "$JAVACMD" )
176 |
177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
178 | for arg do
179 | if
180 | case $arg in #(
181 | -*) false ;; # don't mess with options #(
182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
183 | [ -e "$t" ] ;; #(
184 | *) false ;;
185 | esac
186 | then
187 | arg=$( cygpath --path --ignore --mixed "$arg" )
188 | fi
189 | # Roll the args list around exactly as many times as the number of
190 | # args, so each arg winds up back in the position where it started, but
191 | # possibly modified.
192 | #
193 | # NB: a `for` loop captures its iteration list before it begins, so
194 | # changing the positional parameters here affects neither the number of
195 | # iterations, nor the values presented in `arg`.
196 | shift # remove old arg
197 | set -- "$@" "$arg" # push replacement arg
198 | done
199 | fi
200 |
201 |
202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
204 |
205 | # Collect all arguments for the java command:
206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
207 | # and any embedded shellness will be escaped.
208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
209 | # treated as '${Hostname}' itself on the command line.
210 |
211 | set -- \
212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
213 | -classpath "$CLASSPATH" \
214 | org.gradle.wrapper.GradleWrapperMain \
215 | "$@"
216 |
217 | # Stop when "xargs" is not available.
218 | if ! command -v xargs >/dev/null 2>&1
219 | then
220 | die "xargs is not available"
221 | fi
222 |
223 | # Use "xargs" to parse quoted args.
224 | #
225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
226 | #
227 | # In Bash we could simply go:
228 | #
229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
230 | # set -- "${ARGS[@]}" "$@"
231 | #
232 | # but POSIX shell has neither arrays nor command substitution, so instead we
233 | # post-process each arg (as a line of input to sed) to backslash-escape any
234 | # character that might be a shell metacharacter, then use eval to reverse
235 | # that process (while maintaining the separation between arguments), and wrap
236 | # the whole thing up as a single "set" statement.
237 | #
238 | # This will of course break if any of these variables contains a newline or
239 | # an unmatched quote.
240 | #
241 |
242 | eval "set -- $(
243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
244 | xargs -n1 |
245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
246 | tr '\n' ' '
247 | )" '"$@"'
248 |
249 | exec "$JAVACMD" "$@"
250 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%"=="" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%"=="" set DIRNAME=.
29 | @rem This is normally unused
30 | set APP_BASE_NAME=%~n0
31 | set APP_HOME=%DIRNAME%
32 |
33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
35 |
36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
38 |
39 | @rem Find java.exe
40 | if defined JAVA_HOME goto findJavaFromJavaHome
41 |
42 | set JAVA_EXE=java.exe
43 | %JAVA_EXE% -version >NUL 2>&1
44 | if %ERRORLEVEL% equ 0 goto execute
45 |
46 | echo. 1>&2
47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
48 | echo. 1>&2
49 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
50 | echo location of your Java installation. 1>&2
51 |
52 | goto fail
53 |
54 | :findJavaFromJavaHome
55 | set JAVA_HOME=%JAVA_HOME:"=%
56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
57 |
58 | if exist "%JAVA_EXE%" goto execute
59 |
60 | echo. 1>&2
61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
62 | echo. 1>&2
63 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
64 | echo location of your Java installation. 1>&2
65 |
66 | goto fail
67 |
68 | :execute
69 | @rem Setup the command line
70 |
71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
72 |
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if %ERRORLEVEL% equ 0 goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | set EXIT_CODE=%ERRORLEVEL%
85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
87 | exit /b %EXIT_CODE%
88 |
89 | :mainEnd
90 | if "%OS%"=="Windows_NT" endlocal
91 |
92 | :omega
93 |
--------------------------------------------------------------------------------
/scripts/publish.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | true
4 |
--------------------------------------------------------------------------------
/scripts/verify.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
6 | cd $DIR/..
7 |
8 | export JAVA_HOME=$JAVA_1_7_HOME
9 | ./gradlew check
10 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'gradle-python-envs'
2 |
--------------------------------------------------------------------------------
/src/main/groovy/com/jetbrains/python/envs/PythonEnvsExtension.groovy:
--------------------------------------------------------------------------------
1 | package com.jetbrains.python.envs
2 |
3 |
4 | import org.gradle.api.InvalidUserDataException
5 |
6 | /**
7 | * Project extension to configure Python build environment.
8 | *
9 | */
10 | class PythonEnvsExtension {
11 | File bootstrapDirectory
12 | File envsDirectory
13 |
14 | URL zipRepository
15 | Boolean shouldUseZipsFromRepository = false
16 |
17 | Boolean _64Bits = true // By default 64 bit envs should be installed
18 | String condaDefaultVersion = "Miniconda2-latest"
19 | String pypyDefaultVersion = "pypy2.7-5.8.0"
20 | @SuppressWarnings("unused")
21 | String pipInstallOptions = "--trusted-host pypi.python.org --trusted-host pypi.org --trusted-host files.pythonhosted.org"
22 |
23 | List pythons = []
24 | List condas = []
25 | List condaEnvs = []
26 | List virtualEnvs = []
27 | List pythonsFromZip = []
28 |
29 | String CONDA_PREFIX = "CONDA_"
30 |
31 | /**
32 | * @param envName name of environment like "env_for_django"
33 | * @param version py version like "3.4"
34 | * @param packages collection of py packages to install
35 | * @param patchFileUri URI of a patch to apply when building Python (see the `python-build`'s `-p` option). Absolute paths are also accepted.
36 | */
37 | void python(final String envName,
38 | final String version,
39 | final String architecture = null,
40 | final List packages = null,
41 | final String patchFileUri = null) {
42 | if (zipRepository && shouldUseZipsFromRepository) {
43 | if (patchFileUri) {
44 | throw new InvalidUserDataException("A patch is defined for a pre-built Python")
45 | }
46 | pythonFromZip envName, getUrlFromRepository("python", version, architecture), "python", packages
47 | } else {
48 | pythons << new Python(envName, bootstrapDirectory, EnvType.PYTHON, version, is64(architecture), packages, null, patchFileUri)
49 | }
50 | }
51 |
52 | void python(final String envName, final String version, final List packages) {
53 | python(envName, version, null, packages)
54 | }
55 |
56 | /**
57 | * @see #python
58 | * @param urlToArchive URL link to archive with environment
59 | */
60 | void pythonFromZip(final String envName,
61 | final URL urlToArchive,
62 | final String type = null,
63 | final List packages = null) {
64 | pythonsFromZip << new Python(
65 | envName,
66 | bootstrapDirectory,
67 | EnvType.fromString(type),
68 | null,
69 | null,
70 | packages,
71 | urlToArchive
72 | )
73 | }
74 |
75 | /**
76 | * @see #python
77 | * @param sourceEnvName name of inherited environment like "env_for_django"
78 | */
79 | void virtualenv(final String envName, final String sourceEnvName, final List packages = null) {
80 | Python pythonEnv = (pythons + pythonsFromZip).find { it.name == sourceEnvName }
81 | if (pythonEnv != null) {
82 | virtualEnvs << new VirtualEnv(envName, envsDirectory, pythonEnv, packages)
83 | } else {
84 | println("Specified environment '$sourceEnvName' for virtualenv '$envName' isn't found")
85 | }
86 | }
87 |
88 | /**
89 | * @see #python
90 | */
91 | void conda(final String envName,
92 | final String version,
93 | final String architecture,
94 | final List packages = null) {
95 | List pipPackages = packages.findAll { !it.startsWith(CONDA_PREFIX) }
96 | List condaPackages = packages.findAll { it.startsWith(CONDA_PREFIX) }
97 | .collect { it.substring(CONDA_PREFIX.length()) }
98 | condas << new Conda(envName, bootstrapDirectory, version, is64(architecture), pipPackages, condaPackages)
99 | }
100 |
101 | void conda(final String envName,
102 | final String version,
103 | final List packages = null) {
104 | conda(envName, version, null, packages)
105 | }
106 |
107 | void conda(final String envName,
108 | final List packages = null) {
109 | conda(envName, condaDefaultVersion, null, packages)
110 | }
111 |
112 | /**
113 | * @see #python
114 | * @param sourceEnvName name of inherited environment like "env_for_django"
115 | */
116 | void condaenv(final String envName,
117 | final String version,
118 | final String sourceEnvName = null,
119 | final List packages = null) {
120 | List pipPackages = packages.findAll { !it.startsWith(CONDA_PREFIX) }
121 | List condaPackages = packages.findAll { it.startsWith(CONDA_PREFIX) }
122 | .collect { it.substring(CONDA_PREFIX.length()) }
123 | if (sourceEnvName == null) {
124 | conda condaDefaultVersion
125 | }
126 | Conda condaEnv = condas.find { it.name == sourceEnvName ?: condaDefaultVersion }
127 |
128 | if (condaEnv != null) {
129 | condaEnvs << new CondaEnv(envName, envsDirectory, condaEnv, version, pipPackages, condaPackages)
130 | } else {
131 | println("Specified environment '$sourceEnvName' for condaenv '$envName' isn't found")
132 | }
133 | }
134 |
135 | void condaenv(final String envName,
136 | final String version,
137 | final List packages) {
138 | condaenv(envName, version, null, packages)
139 | }
140 |
141 | /**
142 | * @see #python
143 | */
144 | void jython(final String envName, final List packages = null) {
145 | pythons << new Python(envName, bootstrapDirectory, EnvType.JYTHON, null, null, packages)
146 | }
147 |
148 | /**
149 | * @see #python
150 | */
151 | void pypy(final String envName, final String version = null, final List packages = null) {
152 | pythons << new Python(
153 | envName,
154 | bootstrapDirectory,
155 | EnvType.PYPY,
156 | version ?: pypyDefaultVersion,
157 | null,
158 | packages
159 | )
160 | }
161 |
162 | void pypy(final String envName, final List packages) {
163 | pypy(envName, null, packages)
164 | }
165 |
166 | /**
167 | * @see #python
168 | */
169 | void ironpython(final String envName,
170 | final String architecture = null,
171 | final List packages = null,
172 | final URL urlToArchive = null) {
173 | URL urlToIronPythonZip = new URL("https://github.com/IronLanguages/ironpython2/releases/download/ipy-2.7.9/IronPython.2.7.9.zip")
174 | pythonsFromZip << new Python(
175 | envName,
176 | bootstrapDirectory,
177 | EnvType.IRONPYTHON,
178 | null,
179 | is64(architecture),
180 | packages,
181 | urlToArchive ?: urlToIronPythonZip
182 | )
183 | }
184 |
185 | void ironpython(final String envName,
186 | final List packages,
187 | final URL urlToArchive = null) {
188 | ironpython(envName, null, packages, urlToArchive)
189 | }
190 |
191 | String condaPackage(final String packageName) {
192 | return CONDA_PREFIX + packageName
193 | }
194 |
195 | private Boolean is64(final String architecture) {
196 | return (architecture == null) ? _64Bits : !(architecture == "32")
197 | }
198 |
199 | private URL getUrlFromRepository(final String type, final String version, final String architecture = null) {
200 | if (!zipRepository) return null
201 | return zipRepository.toURI().resolve("$type-$version-${architecture ?: _64Bits ? "64" : "32"}.zip").toURL()
202 | }
203 | }
204 |
205 |
206 | enum EnvType {
207 | PYTHON,
208 | CONDA,
209 | JYTHON,
210 | PYPY,
211 | IRONPYTHON,
212 | VIRTUALENV
213 | // TODO non-python virtualenv?
214 |
215 | static fromString(String type) {
216 | return type == null ? null : valueOf(type.toUpperCase())
217 | }
218 | }
219 |
220 |
221 | class Python {
222 | final String name
223 | final File envDir
224 | final EnvType type
225 | final String version
226 | final Boolean is64
227 | final List packages
228 | final URL url
229 | final String patchFileUri
230 |
231 | Python(String name,
232 | File dir,
233 | EnvType type = null,
234 | String version = null,
235 | Boolean is64 = true,
236 | List packages = null,
237 | URL url = null,
238 | String patchFileUri = null) {
239 | this.name = name
240 | this.envDir = new File(dir, name)
241 | this.type = type
242 | this.version = version
243 | this.is64 = is64
244 | this.packages = packages
245 | this.url = url
246 | this.patchFileUri = patchFileUri
247 | }
248 | }
249 |
250 |
251 | class VirtualEnv extends Python {
252 | final Python sourceEnv
253 |
254 | VirtualEnv(String name, File dir, Python sourceEnv, List packages) {
255 | super(name, dir, EnvType.VIRTUALENV, sourceEnv.version, sourceEnv.is64, packages)
256 | this.sourceEnv = sourceEnv
257 | }
258 | }
259 |
260 |
261 | class Conda extends Python {
262 | final List condaPackages
263 |
264 | Conda(String name,
265 | File dir,
266 | String version,
267 | Boolean is64,
268 | List pipPackages,
269 | List condaPackages) {
270 | super(name, dir, EnvType.CONDA, version, is64, pipPackages)
271 | this.condaPackages = condaPackages
272 | }
273 | }
274 |
275 |
276 | class CondaEnv extends Conda {
277 | final Conda sourceEnv
278 |
279 | CondaEnv(String name,
280 | File dir,
281 | Conda sourceEnv,
282 | String version,
283 | List pipPackages,
284 | List condaPackages) {
285 | super(name, dir, version, sourceEnv.is64, pipPackages, condaPackages)
286 | this.sourceEnv = sourceEnv
287 | }
288 | }
289 |
290 |
--------------------------------------------------------------------------------
/src/main/groovy/com/jetbrains/python/envs/PythonEnvsPlugin.groovy:
--------------------------------------------------------------------------------
1 | package com.jetbrains.python.envs
2 |
3 | import org.apache.tools.ant.filters.StringInputStream
4 | import org.apache.tools.ant.taskdefs.condition.Os
5 | import org.gradle.api.GradleException
6 | import org.gradle.api.Plugin
7 | import org.gradle.api.Project
8 | import org.gradle.api.Task
9 | import org.gradle.util.VersionNumber
10 |
11 | import java.nio.file.Paths
12 |
13 | class PythonEnvsPlugin implements Plugin {
14 | private static String osName = System.getProperty('os.name').replaceAll(' ', '').with {
15 | return it.contains("Windows") ? "Windows" : it
16 | }
17 |
18 | private static Boolean isWindows = Os.isFamily(Os.FAMILY_WINDOWS)
19 | private static Boolean isUnix = Os.isFamily(Os.FAMILY_UNIX)
20 | private static Boolean isMacOsX = Os.isFamily(Os.FAMILY_MAC)
21 |
22 | private static URL getUrlToDownloadConda(Conda conda) {
23 | final String repository = (conda.version.toLowerCase().contains("miniconda")) ? "miniconda" : "archive"
24 | final String arch = getArch()
25 | final String ext = isWindows ? "exe" : "sh"
26 |
27 | return new URL("https://repo.continuum.io/$repository/${conda.version}-$osName-$arch.$ext")
28 | }
29 |
30 | private static String getArch() {
31 | def arch = System.getProperty("os.arch")
32 | switch (arch) {
33 | case ~/x86|i386|ia-32|i686/:
34 | arch = "x86"
35 | break
36 | case ~/x86_64|amd64|x64|x86-64/:
37 | arch = "x86_64"
38 | break
39 | case ~/arm|arm-v7|armv7|arm32/:
40 | arch = "armv7l"
41 | break
42 | case ~/aarch64|arm64|arm-v8/:
43 | arch = isMacOsX ? "arm64" : "aarch64"
44 | break
45 | }
46 | return arch
47 | }
48 |
49 | private static File getExecutable(String executable, Python env = null, File dir = null, EnvType type = null) {
50 | String pathString
51 |
52 | switch (type ?: env.type) {
53 | case [EnvType.PYTHON, EnvType.CONDA]:
54 | if (executable in ["pip", "virtualenv", "conda"]) {
55 | pathString = isWindows ? "Scripts/${executable}.exe" : "bin/${executable}"
56 | } else if (executable.startsWith("python")) {
57 | pathString = isWindows ? "${executable}.exe" : "bin/${executable}"
58 | } else {
59 | throw new RuntimeException("$executable is not supported for $env.type yet")
60 | }
61 | break
62 | case [EnvType.JYTHON, EnvType.PYPY]:
63 | if (env.type == EnvType.JYTHON && executable == "python") executable = "jython"
64 | pathString = "bin/${executable}${isWindows ? '.exe' : ''}"
65 | break
66 | case EnvType.IRONPYTHON:
67 | if (executable in ["ipy", "python"] ) {
68 | pathString = "net45/${env.is64 ? "ipy.exe" : "ipy32.exe"}"
69 | } else {
70 | pathString = "Scripts/${executable}.exe"
71 | }
72 | break
73 | case EnvType.VIRTUALENV:
74 | pathString = isWindows ? "Scripts/${executable}.exe" : "bin/${executable}"
75 | break
76 | default:
77 | throw new RuntimeException("$env.type env type is not supported yet")
78 | }
79 |
80 | return new File(dir ?: env.envDir, pathString)
81 | }
82 |
83 | private static File getPipFile(Project project) {
84 | new File(project.buildDir, "get-pip.py").with { file ->
85 | if (!file.exists()) {
86 | project.ant.get(dest: file) {
87 | url(url: "https://bootstrap.pypa.io/get-pip.py")
88 | }
89 | }
90 | return file
91 | }
92 | }
93 |
94 | private static Task createInstallPythonBuildTask(Project project, File installDir) {
95 | return project.tasks.create(name: 'install_python_build') {
96 |
97 | onlyIf {
98 | isUnix && !installDir.exists()
99 | }
100 |
101 | doFirst {
102 | project.buildDir.mkdirs()
103 | }
104 |
105 | doLast {
106 | new File(project.buildDir, "pyenv.zip").with { pyenvZip ->
107 | project.logger.quiet("Downloading latest pyenv from github")
108 | project.ant.get(dest: pyenvZip) {
109 | url(url: "https://github.com/pyenv/pyenv/archive/master.zip")
110 | }
111 |
112 | File unzipFolder = new File(project.buildDir, "python-build-tmp")
113 | String pathToPythonBuildInPyenv = "pyenv-master/plugins/python-build"
114 |
115 | project.logger.quiet("Unzipping python-build to $unzipFolder")
116 | project.copy {
117 | from project.zipTree(pyenvZip)
118 | into unzipFolder
119 | include "$pathToPythonBuildInPyenv/**"
120 | eachFile { file ->
121 | file.path = file.path.replaceFirst(pathToPythonBuildInPyenv, '')
122 | }
123 | }
124 |
125 | project.logger.quiet("Installing python-build via bash to $installDir")
126 | project.exec {
127 | commandLine "bash", new File(unzipFolder, "install.sh")
128 | environment PREFIX: installDir
129 | }
130 |
131 | project.logger.quiet("Removing garbage")
132 | unzipFolder.deleteDir()
133 | pyenvZip.delete()
134 | }
135 | }
136 | }
137 | }
138 |
139 | private Task createPythonUnixTask(Project project, Python env) {
140 |
141 | return project.tasks.create(name: "Bootstrap_${env.type}_'$env.name'") {
142 |
143 | dependsOn "install_python_build"
144 |
145 | onlyIf {
146 | isUnix && (!env.envDir.exists() || isPythonInvalid(project, env))
147 | }
148 |
149 | doFirst {
150 | env.envDir.mkdirs()
151 | env.envDir.deleteDir()
152 | }
153 |
154 | doLast {
155 | project.logger.quiet("Creating $env.type '$env.name' at $env.envDir directory")
156 | try {
157 | project.exec {
158 | executable new File(project.buildDir, "python-build/bin/python-build")
159 | if (env.patchFileUri != null) {
160 | project.logger.quiet("Applying patch from ${env.patchFileUri} to ${env.name}")
161 | if (Paths.get(env.patchFileUri).isAbsolute()) {
162 | standardInput = new FileInputStream(env.patchFileUri)
163 | }
164 | else {
165 | standardInput = new StringInputStream(new URI(env.patchFileUri).toURL().text)
166 | }
167 | args "-p", env.version, env.envDir
168 | }
169 | else {
170 | args env.version, env.envDir
171 | }
172 | }
173 | project.logger.quiet("Successfully")
174 | }
175 | catch (Exception e) {
176 | if (isPythonInvalid(project, env)) {
177 | project.logger.error(e.message)
178 | throw new GradleException(e.message)
179 | } else {
180 | project.logger.warn(e.message)
181 | }
182 | }
183 |
184 | upgradePipAndSetuptools(project, env)
185 | pipInstall(project, env, env.packages)
186 | }
187 | }
188 | }
189 |
190 | private Task createPythonWindowsTask(Project project, Python env) {
191 | return project.tasks.create(name: "Bootstrap_${env.type}_'$env.name'") {
192 | onlyIf {
193 | isWindows && (!env.envDir.exists() || isPythonInvalid(project, env))
194 | }
195 |
196 | doFirst {
197 | project.buildDir.mkdir()
198 | env.envDir.mkdirs()
199 | env.envDir.deleteDir()
200 | }
201 |
202 | doLast {
203 | project.logger.quiet("Creating $env.type '$env.name' at $env.envDir directory")
204 |
205 | try {
206 | String extension = VersionNumber.parse(env.version) >= VersionNumber.parse("3.5.0") ? "exe" : "msi"
207 | String filename = "python-${env.version}${env.is64 ? (extension == "msi" ? "." : "-") + "amd64" : ""}.$extension"
208 | File installer = new File(project.buildDir, filename)
209 |
210 | project.logger.quiet("Downloading $installer")
211 | project.ant.get(dest: installer) {
212 | url(url: "https://www.python.org/ftp/python/${env.version}/$filename")
213 | }
214 |
215 | project.logger.quiet("Installing $env.name")
216 | if (extension == "msi") {
217 | project.exec {
218 | commandLine "msiexec", "/i", installer, "/quiet", "TARGETDIR=$env.envDir.absolutePath"
219 | }
220 | } else if (extension == "exe") {
221 | project.mkdir(env.envDir)
222 | project.exec {
223 | executable installer
224 | args installer, "/i", "/quiet", "TargetDir=$env.envDir.absolutePath", "Include_launcher=0",
225 | "InstallLauncherAllUsers=0", "Shortcuts=0", "AssociateFiles=0"
226 | }
227 | }
228 |
229 | if (!getExecutable("pip", env).exists()) {
230 | project.logger.quiet("Downloading & installing pip and setuptools")
231 | project.exec {
232 | executable getExecutable("python", env)
233 | args getPipFile(project)
234 | }
235 | }
236 | // It's better to save installer for good uninstall
237 | // installer.delete()
238 | }
239 | catch (Exception e) {
240 | project.logger.error(e.message)
241 | throw new GradleException(e.message)
242 | }
243 |
244 | pipInstall(project, env, env.packages)
245 | }
246 | }
247 | }
248 |
249 | private Task createJythonTask(Project project, Python env) {
250 | return project.tasks.create(name: "Bootstrap_${env.type}_'$env.name'") {
251 | onlyIf {
252 | !env.envDir.exists() || isPythonInvalid(project, env)
253 | }
254 |
255 | doFirst {
256 | env.envDir.deleteDir()
257 | }
258 |
259 | doLast {
260 | project.logger.quiet("Creating $env.type '$env.name' at $env.envDir directory")
261 |
262 | project.javaexec {
263 | main = '-jar'
264 | args project.configurations.jython.singleFile, '-s', '-d', env.envDir, '-t', 'standard'
265 | }
266 |
267 | pipInstall(project, env, env.packages)
268 | }
269 | }
270 | }
271 |
272 | @Override
273 | void apply(Project project) {
274 | PythonEnvsExtension envs = project.extensions.create("envs", PythonEnvsExtension.class)
275 |
276 | project.repositories {
277 | mavenCentral()
278 | }
279 |
280 | project.configurations {
281 | jython
282 | }
283 |
284 | project.afterEvaluate {
285 | project.configurations.jython.incoming.beforeResolve {
286 | project.dependencies {
287 | jython group: 'org.python', name: 'jython-installer', version: '2.7.1'
288 | }
289 | }
290 |
291 | createInstallPythonBuildTask(project, new File(project.buildDir, "python-build"))
292 |
293 | Task python_task = project.tasks.create(name: 'build_pythons') {
294 |
295 | onlyIf { !envs.pythons.empty }
296 |
297 | envs.pythons.each { env ->
298 | switch (env.type) {
299 | case EnvType.PYTHON:
300 | if (isUnix) {
301 | dependsOn createPythonUnixTask(project, env)
302 | } else if (isWindows) {
303 | dependsOn createPythonWindowsTask(project, env)
304 | } else {
305 | project.logger.error("Something is wrong with os: $osName")
306 | }
307 | break
308 | case EnvType.JYTHON:
309 | dependsOn createJythonTask(project, env)
310 | break
311 | case EnvType.PYPY:
312 | if (isUnix) {
313 | dependsOn createPythonUnixTask(project, env)
314 | } else {
315 | project.logger.warn("PyPy installation isn't supported for $osName, please use envFromZip instead")
316 | }
317 | break
318 | default:
319 | project.logger.error("$env.type isn't supported yet")
320 | }
321 | }
322 | }
323 |
324 | Task python_from_zip_task = project.tasks.create(name: 'build_pythons_from_zip') {
325 |
326 | onlyIf { !envs.pythonsFromZip.empty }
327 |
328 | envs.pythonsFromZip.each { env ->
329 | dependsOn project.tasks.create(name: "Bootstrap_${env.type ? env.type : ''}_'$env.name'_from_archive") {
330 | onlyIf {
331 | !env.envDir.exists() || isPythonInvalid(project, env)
332 | }
333 |
334 | doFirst {
335 | project.buildDir.mkdir()
336 | env.envDir.mkdirs()
337 | env.envDir.deleteDir()
338 | }
339 |
340 | doLast {
341 | try {
342 | String archiveName = env.url.toString().with { urlString ->
343 | urlString.substring(urlString.lastIndexOf('/') + 1, urlString.length())
344 | }
345 | if (!archiveName.endsWith("zip")) {
346 | throw new RuntimeException("Wrong archive extension, only zip is supported")
347 | }
348 |
349 | File zipArchive = new File(project.buildDir, archiveName)
350 | project.logger.quiet("Downloading $archiveName archive from $env.url")
351 | project.ant.get(dest: zipArchive) {
352 | url(url: env.url)
353 | }
354 |
355 | project.logger.quiet("Unzipping downloaded $archiveName archive")
356 | project.ant.unzip(src: zipArchive, dest: env.envDir)
357 |
358 | env.envDir.with { dir ->
359 | if (dir.listFiles().length == 1) {
360 | File intermediateDir = dir.listFiles().last()
361 | if (!intermediateDir.isDirectory()) {
362 | throw new RuntimeException("Archive is wrong, $env.url")
363 | }
364 | project.ant.move(todir: dir) {
365 | fileset(dir: intermediateDir)
366 | }
367 | } else {
368 | return dir
369 | }
370 | }
371 |
372 | if (env.type != null) {
373 | if (!getExecutable("pip", env).exists()) {
374 | project.logger.quiet("Downloading & installing pip and setuptools")
375 | project.exec {
376 | if (env.type == EnvType.IRONPYTHON) {
377 | executable getExecutable("ipy", env)
378 | args "-m", "ensurepip"
379 | } else {
380 | executable getExecutable("python", env)
381 | args getPipFile(project)
382 | }
383 | }
384 | }
385 | upgradePipAndSetuptools(project, env)
386 | }
387 |
388 | project.logger.quiet("Deleting $archiveName archive")
389 | zipArchive.delete()
390 | }
391 | catch (Exception e) {
392 | project.logger.error(e.message)
393 | throw new GradleException(e.message)
394 | }
395 |
396 | pipInstall(project, env, env.packages)
397 | }
398 | }
399 | }
400 | }
401 |
402 | Task virtualenvs_task = project.tasks.create(name: 'build_virtual_envs') {
403 | shouldRunAfter python_task, python_from_zip_task
404 |
405 | onlyIf { !envs.virtualEnvs.empty }
406 |
407 | envs.virtualEnvs.each { env ->
408 | if (env.sourceEnv.type == EnvType.IRONPYTHON) {
409 | project.logger.warn("IronPython doesn't support virtualenvs")
410 | return
411 | }
412 |
413 | dependsOn project.tasks.create("Create_virtualenv_'$env.name'") {
414 | onlyIf {
415 | (!env.envDir.exists() || isPythonInvalid(project, env)) && env.sourceEnv.type != null
416 | }
417 |
418 | doFirst {
419 | env.envDir.mkdirs()
420 | env.envDir.deleteDir()
421 | }
422 |
423 | doLast {
424 | project.logger.quiet("Installing needed virtualenv package")
425 |
426 | pipInstall(project, env.sourceEnv, ["virtualenv"])
427 |
428 | project.logger.quiet("Creating virtualenv from $env.sourceEnv.name at $env.envDir")
429 | project.exec {
430 | executable getExecutable("virtualenv", env.sourceEnv)
431 | args env.envDir, "--always-copy"
432 | workingDir env.sourceEnv.envDir
433 | }
434 |
435 | pipInstall(project, env, env.packages)
436 | }
437 | }
438 | }
439 | }
440 |
441 | Task conda_task = project.tasks.create(name: "build_condas") {
442 |
443 | onlyIf { !envs.condas.empty }
444 |
445 | envs.condas.each { Conda env ->
446 | dependsOn project.tasks.create(name: "Bootstrap_${env.type}_'$env.name'") {
447 | onlyIf {
448 | !env.envDir.exists() || isPythonInvalid(project, env)
449 | }
450 |
451 | doFirst {
452 | project.buildDir.mkdir()
453 | env.envDir.mkdirs()
454 | env.envDir.deleteDir()
455 | }
456 |
457 | doLast {
458 | URL urlToConda = getUrlToDownloadConda(env)
459 | File installer = new File(project.buildDir, urlToConda.toString().split("/").last())
460 |
461 | if (!installer.exists()) {
462 | project.logger.quiet("Downloading $installer.name")
463 | project.ant.get(dest: installer) {
464 | url(url: urlToConda)
465 | }
466 | }
467 |
468 | project.logger.quiet("Bootstraping to $env.envDir")
469 | project.exec {
470 | if (isWindows) {
471 | commandLine installer, "/InstallationType=JustMe", "/AddToPath=0", "/RegisterPython=0", "/S", "/D=$env.envDir"
472 | } else {
473 | commandLine "bash", installer, "-b", "-p", env.envDir
474 | }
475 | }
476 |
477 | pipInstall(project, env, env.packages)
478 | condaInstall(project, env, env.condaPackages)
479 | }
480 | }
481 | }
482 | }
483 |
484 | Task conda_envs_task = project.tasks.create(name: 'build_conda_envs') {
485 | shouldRunAfter conda_task
486 |
487 | onlyIf { !envs.condaEnvs.empty }
488 |
489 | envs.condaEnvs.each { env ->
490 | dependsOn project.tasks.create("Create_conda_env_'$env.name'") {
491 | onlyIf {
492 | !env.envDir.exists() || isPythonInvalid(project, env)
493 | }
494 |
495 | doFirst {
496 | env.envDir.mkdirs()
497 | env.envDir.deleteDir()
498 | }
499 |
500 | doLast {
501 | project.logger.quiet("Creating condaenv '$env.name' at $env.envDir directory")
502 | project.exec {
503 | executable getExecutable("conda", env.sourceEnv)
504 | args "create", "-p", env.envDir, "-y", "python=$env.version"
505 | args env.condaPackages
506 | }
507 |
508 | pipInstall(project, env, env.packages)
509 | }
510 | }
511 | }
512 | }
513 |
514 | project.tasks.create(name: 'build_envs') {
515 | dependsOn python_task,
516 | python_from_zip_task,
517 | virtualenvs_task,
518 | conda_task,
519 | conda_envs_task
520 | }
521 | }
522 | }
523 |
524 | private void upgradePipAndSetuptools(Project project, Python env) {
525 | project.logger.quiet("Force upgrade pip and setuptools")
526 | List command = [
527 | getExecutable("python", env), "-m", "pip", "install",
528 | *project.extensions.findByName("envs").getProperty("pipInstallOptions").split(" "),
529 | "--upgrade", "--force", "pip", "setuptools"
530 | ]
531 |
532 | project.logger.quiet("Executing '${command.join(" ")}'")
533 | if (project.exec {
534 | commandLine command
535 | }.exitValue != 0) throw new GradleException("pip & setuptools upgrade failed")
536 | }
537 |
538 | private void pipInstall(Project project, Python env, List packages) {
539 | if (packages == null || packages.empty || env.type == null) {
540 | return
541 | }
542 | project.logger.quiet("Installing packages via pip: $packages")
543 |
544 | List command = [
545 | getExecutable("pip", env),
546 | "install",
547 | *project.extensions.findByName("envs").getProperty("pipInstallOptions").split(" "),
548 | *packages
549 | ]
550 | project.logger.quiet("Executing '${command.join(" ")}'")
551 |
552 | if (project.exec {
553 | commandLine command
554 | }.exitValue != 0) throw new GradleException("pip install failed")
555 | }
556 |
557 | private void condaInstall(Project project, Conda conda, List packages) {
558 | if (packages == null || packages.empty) {
559 | return
560 | }
561 | project.logger.quiet("Installing packages via conda: $packages")
562 |
563 | List command = [
564 | getExecutable("conda", conda),
565 | "install", "-y",
566 | "-p", conda.envDir,
567 | *packages
568 | ]
569 | project.logger.quiet("Executing '${command.join(" ")}'")
570 |
571 | if (project.exec {
572 | commandLine command
573 | }.exitValue != 0) throw new GradleException("conda install failed")
574 | }
575 |
576 | private boolean isPythonValid(Project project, Python env) {
577 | File exec = getExecutable("python", env)
578 | if (!exec.exists()) return false
579 |
580 | int exitValue
581 | try {
582 | exitValue = project.exec { commandLine exec, "-c", "'print(1)'" }.exitValue
583 | } catch (ignored) {
584 | return false
585 | }
586 |
587 | return exitValue == 0
588 | }
589 |
590 | private boolean isPythonInvalid(Project project, Python env) {
591 | return !isPythonValid(project, env)
592 | }
593 | }
594 |
--------------------------------------------------------------------------------
/src/test/groovy/com/jetbrains/python/envs/test/FunctionalTest.groovy:
--------------------------------------------------------------------------------
1 | package com.jetbrains.python.envs.test
2 |
3 | import org.gradle.testkit.runner.BuildResult
4 | import org.gradle.testkit.runner.GradleRunner
5 | import spock.lang.TempDir
6 |
7 | import java.nio.file.Files
8 | import java.nio.file.Path
9 | import spock.lang.Requires
10 | import spock.lang.Specification
11 |
12 | import static org.gradle.testkit.runner.TaskOutcome.SUCCESS
13 | import static org.gradle.testkit.runner.TaskOutcome.UP_TO_DATE
14 |
15 | class FunctionalTest extends Specification {
16 | @TempDir
17 | Path testProjectDir
18 | File settingsFile
19 | File buildFile
20 |
21 | def setup() {
22 | settingsFile = Files.createFile(testProjectDir.resolve('settings.gradle')).toFile()
23 | buildFile = Files.createFile(testProjectDir.resolve('build.gradle')).toFile()
24 | }
25 |
26 | def "install python 2.7.15 and virtualenv"() {
27 | given:
28 | settingsFile << "rootProject.name = 'gradle-python-envs'"
29 | buildFile << """
30 | plugins {
31 | id "com.jetbrains.python.envs"
32 | }
33 |
34 | envs {
35 | bootstrapDirectory = new File(buildDir, 'bootstrap')
36 | envsDirectory = file(buildDir)
37 |
38 | python "python-2.7.15", "2.7.15"
39 | virtualenv "virtualenv-2.7.15", "python-2.7.15"
40 | }
41 |
42 | """
43 |
44 | when:
45 | BuildResult result = GradleRunner.create()
46 | .withProjectDir(testProjectDir.toFile())
47 | .withArguments('build_envs')
48 | .withPluginClasspath()
49 | .build()
50 |
51 | then:
52 | println(result.output)
53 | result.output.contains('Installing Python-2.7.15')
54 | result.output.contains('BUILD SUCCESSFUL')
55 | result.task(":build_envs").outcome == SUCCESS
56 | }
57 |
58 | def "install python 3.7.1 and virtualenv"() {
59 | given:
60 | settingsFile << "rootProject.name = 'gradle-python-envs'"
61 | buildFile << """
62 | plugins {
63 | id "com.jetbrains.python.envs"
64 | }
65 |
66 | envs {
67 | bootstrapDirectory = new File(buildDir, 'bootstrap')
68 | envsDirectory = file(buildDir)
69 |
70 | python "python-3.7.1", "3.7.1"
71 | virtualenv "virtualenv-3.7.1", "python-3.7.1"
72 | }
73 |
74 | """
75 |
76 | when:
77 | BuildResult result = GradleRunner.create()
78 | .withProjectDir(testProjectDir.toFile())
79 | .withArguments('build_envs')
80 | .withPluginClasspath()
81 | .build()
82 |
83 | then:
84 | println(result.output)
85 | result.output.contains('Installing Python-3.7.1')
86 | result.output.contains('BUILD SUCCESSFUL')
87 | result.task(":build_envs").outcome == SUCCESS
88 | }
89 |
90 | def "install python 3.6.5 32-bit and virtualenv"() {
91 | given:
92 | settingsFile << "rootProject.name = 'gradle-python-envs'"
93 | buildFile << """
94 | plugins {
95 | id "com.jetbrains.python.envs"
96 | }
97 |
98 | envs {
99 | bootstrapDirectory = new File(buildDir, 'bootstrap')
100 | envsDirectory = file(buildDir)
101 |
102 | python "python-3.6.5-32", "3.6.5", "32"
103 | virtualenv "virtualenv-3.6.5", "python-3.6.5-32"
104 | }
105 |
106 | """
107 |
108 | when:
109 | BuildResult result = GradleRunner.create()
110 | .withProjectDir(testProjectDir.toFile())
111 | .withArguments('build_envs')
112 | .withPluginClasspath()
113 | .build()
114 |
115 | then:
116 | println(result.output)
117 | result.output.contains('Installing Python-3.6.5')
118 | result.output.contains('BUILD SUCCESSFUL')
119 | result.task(":build_envs").outcome == SUCCESS
120 | }
121 |
122 | def "install python 3.5.6 with package"() {
123 | given:
124 | settingsFile << "rootProject.name = 'gradle-python-envs'"
125 | buildFile << """
126 | plugins {
127 | id "com.jetbrains.python.envs"
128 | }
129 |
130 | envs {
131 | bootstrapDirectory = new File(buildDir, 'bootstrap')
132 | envsDirectory = file(buildDir)
133 |
134 | python "python-3.5.6", "3.5.6", ["django==1.10"]
135 | }
136 |
137 | """
138 |
139 | when:
140 | BuildResult result = GradleRunner.create()
141 | .withProjectDir(testProjectDir.toFile())
142 | .withArguments('build_envs')
143 | .withPluginClasspath()
144 | .build()
145 |
146 | then:
147 | println(result.output)
148 | result.output.contains('Installing Python-3.5.6')
149 | result.output.contains('BUILD SUCCESSFUL')
150 | result.task(":build_envs").outcome == SUCCESS
151 | }
152 |
153 | def "install latest miniconda3"() {
154 | given:
155 | settingsFile << "rootProject.name = 'gradle-python-envs'"
156 | buildFile << """
157 | plugins {
158 | id "com.jetbrains.python.envs"
159 | }
160 |
161 | envs {
162 | bootstrapDirectory = new File(buildDir, 'bootstrap')
163 | envsDirectory = file(buildDir)
164 |
165 | conda "Miniconda3", "Miniconda3-latest"
166 | }
167 |
168 | """
169 |
170 | when:
171 | BuildResult result = GradleRunner.create()
172 | .withProjectDir(testProjectDir.toFile())
173 | .withArguments('build_envs')
174 | .withPluginClasspath()
175 | .build()
176 |
177 | then:
178 | println(result.output)
179 | result.output.contains('BUILD SUCCESSFUL')
180 | result.task(":build_envs").outcome == SUCCESS
181 | }
182 |
183 | def "install latest miniconda2"() {
184 | given:
185 | settingsFile << "rootProject.name = 'gradle-python-envs'"
186 | buildFile << """
187 | plugins {
188 | id "com.jetbrains.python.envs"
189 | }
190 |
191 | envs {
192 | bootstrapDirectory = new File(buildDir, 'bootstrap')
193 | envsDirectory = file(buildDir)
194 |
195 | conda "Miniconda2", "Miniconda2-latest"
196 | }
197 |
198 | """
199 |
200 | when:
201 | BuildResult result = GradleRunner.create()
202 | .withProjectDir(testProjectDir.toFile())
203 | .withArguments('build_envs')
204 | .withPluginClasspath()
205 | .build()
206 |
207 | then:
208 | println(result.output)
209 | result.output.contains('BUILD SUCCESSFUL')
210 | result.task(":build_envs").outcome == SUCCESS
211 | }
212 |
213 | def "install anaconda2-5.3.1 64 bit with conda package"() {
214 | given:
215 | settingsFile << "rootProject.name = 'gradle-python-envs'"
216 | buildFile << """
217 | plugins {
218 | id "com.jetbrains.python.envs"
219 | }
220 |
221 | envs {
222 | bootstrapDirectory = new File(buildDir, 'bootstrap')
223 | envsDirectory = file(buildDir)
224 |
225 | conda "Anaconda2", "Anaconda2-5.3.1", [condaPackage("PyQt")]
226 | }
227 |
228 | """
229 |
230 | when:
231 | BuildResult result = GradleRunner.create()
232 | .withProjectDir(testProjectDir.toFile())
233 | .withArguments('build_envs')
234 | .withPluginClasspath()
235 | .build()
236 |
237 | then:
238 | println(result.output)
239 | result.output.contains('BUILD SUCCESSFUL')
240 | result.task(":build_envs").outcome == SUCCESS
241 | }
242 |
243 | def "install anaconda3-5.3.1 with python package"() {
244 | given:
245 | settingsFile << "rootProject.name = 'gradle-python-envs'"
246 | buildFile << """
247 | plugins {
248 | id "com.jetbrains.python.envs"
249 | }
250 |
251 | envs {
252 | bootstrapDirectory = new File(buildDir, 'bootstrap')
253 | envsDirectory = file(buildDir)
254 |
255 | conda "Anaconda3", "Anaconda3-5.3.1", ["django==1.8"]
256 | }
257 |
258 | """
259 |
260 | when:
261 | BuildResult result = GradleRunner.create()
262 | .withProjectDir(testProjectDir.toFile())
263 | .withArguments('build_envs')
264 | .withPluginClasspath()
265 | .build()
266 |
267 | then:
268 | println(result.output)
269 | result.output.contains('BUILD SUCCESSFUL')
270 | result.task(":build_envs").outcome == SUCCESS
271 | }
272 |
273 | def "install condaenv with conda package"() {
274 | given:
275 | settingsFile << "rootProject.name = 'gradle-python-envs'"
276 | buildFile << """
277 | plugins {
278 | id "com.jetbrains.python.envs"
279 | }
280 |
281 | envs {
282 | bootstrapDirectory = new File(buildDir, 'bootstrap')
283 | envsDirectory = file(buildDir)
284 |
285 | condaenv "pyqt_env", "2.7", [condaPackage("pyqt")]
286 | }
287 |
288 | """
289 |
290 | when:
291 | BuildResult result = GradleRunner.create()
292 | .withProjectDir(testProjectDir.toFile())
293 | .withArguments('build_envs')
294 | .withPluginClasspath()
295 | .build()
296 |
297 | then:
298 | println(result.output)
299 | result.output.contains('BUILD SUCCESSFUL')
300 | result.task(":build_envs").outcome == SUCCESS
301 | }
302 |
303 | def "install jython and virtualenv"() {
304 | given:
305 | settingsFile << "rootProject.name = 'gradle-python-envs'"
306 | buildFile << """
307 | plugins {
308 | id "com.jetbrains.python.envs"
309 | }
310 |
311 | envs {
312 | bootstrapDirectory = new File(buildDir, 'bootstrap')
313 | envsDirectory = file(buildDir)
314 |
315 | jython "jython"
316 | virtualenv "envJython", "jython"
317 | }
318 |
319 | """
320 |
321 | when:
322 | BuildResult result = GradleRunner.create()
323 | .withProjectDir(testProjectDir.toFile())
324 | .withArguments('build_envs')
325 | .withPluginClasspath()
326 | .build()
327 |
328 | then:
329 | println(result.output)
330 | result.output.contains('BUILD SUCCESSFUL')
331 | result.task(":build_envs").outcome == SUCCESS
332 | }
333 |
334 | @Requires({ os.isLinux() || os.isMacOs() })
335 | def "install pypy2 and virtualenv with package"() {
336 | given:
337 | settingsFile << "rootProject.name = 'gradle-python-envs'"
338 | buildFile << """
339 | plugins {
340 | id "com.jetbrains.python.envs"
341 | }
342 |
343 | envs {
344 | bootstrapDirectory = new File(buildDir, 'bootstrap')
345 | envsDirectory = file(buildDir)
346 |
347 | pypy "pypy2", ["django"]
348 | virtualenv "envPypy2", "pypy2", ["pytest"]
349 | }
350 |
351 | """
352 |
353 | when:
354 | BuildResult result = GradleRunner.create()
355 | .withProjectDir(testProjectDir.toFile())
356 | .withArguments('build_envs')
357 | .withPluginClasspath()
358 | .build()
359 |
360 | then:
361 | println(result.output)
362 | result.output.contains('BUILD SUCCESSFUL')
363 | result.task(":build_envs").outcome in [SUCCESS, UP_TO_DATE]
364 | }
365 |
366 | @Requires({ os.isLinux() || os.isMacOs() })
367 | def "install pypy3 and virtualenv with package"() {
368 | given:
369 | settingsFile << "rootProject.name = 'gradle-python-envs'"
370 | buildFile << """
371 | plugins {
372 | id "com.jetbrains.python.envs"
373 | }
374 |
375 | envs {
376 | bootstrapDirectory = new File(buildDir, 'bootstrap')
377 | envsDirectory = file(buildDir)
378 |
379 | pypy "pypy3", "pypy3.5-5.8.0", ["nose"]
380 | virtualenv "envPypy3", "pypy3", ["django"]
381 | }
382 |
383 | """
384 |
385 | when:
386 | BuildResult result = GradleRunner.create()
387 | .withProjectDir(testProjectDir.toFile())
388 | .withArguments('build_envs')
389 | .withPluginClasspath()
390 | .build()
391 |
392 | then:
393 | println(result.output)
394 | result.output.contains('BUILD SUCCESSFUL')
395 | result.task(":build_envs").outcome in [SUCCESS, UP_TO_DATE]
396 | }
397 |
398 | @Requires({ os.isWindows() })
399 | def "install ironpython32"() {
400 | given:
401 | settingsFile << "rootProject.name = 'gradle-python-envs'"
402 | buildFile << """
403 | plugins {
404 | id "com.jetbrains.python.envs"
405 | }
406 |
407 | envs {
408 | bootstrapDirectory = new File(buildDir, 'bootstrap')
409 | envsDirectory = file(buildDir)
410 |
411 | ironpython "ironpython", "32", ["requests"]
412 | }
413 |
414 | """
415 |
416 | when:
417 | BuildResult result = GradleRunner.create()
418 | .withProjectDir(testProjectDir.toFile())
419 | .withArguments('build_envs')
420 | .withPluginClasspath()
421 | .build()
422 |
423 | then:
424 | println(result.output)
425 | result.output.contains('Downloading IronPython.2.7.9.zip archive')
426 | result.output.contains('BUILD SUCCESSFUL')
427 | result.task(":build_envs").outcome == SUCCESS
428 | }
429 |
430 | @Requires({ os.isWindows() })
431 | def "install ironpython64"() {
432 | given:
433 | settingsFile << "rootProject.name = 'gradle-python-envs'"
434 | buildFile << """
435 | plugins {
436 | id "com.jetbrains.python.envs"
437 | }
438 |
439 | envs {
440 | bootstrapDirectory = new File(buildDir, 'bootstrap')
441 | envsDirectory = file(buildDir)
442 |
443 | ironpython "ironpython64", ["requests"]
444 | }
445 |
446 | """
447 |
448 | when:
449 | BuildResult result = GradleRunner.create()
450 | .withProjectDir(testProjectDir.toFile())
451 | .withArguments('build_envs')
452 | .withPluginClasspath()
453 | .build()
454 |
455 | then:
456 | println(result.output)
457 | result.output.contains('Downloading IronPython.2.7.9.zip archive')
458 | result.output.contains('BUILD SUCCESSFUL')
459 | result.task(":build_envs").outcome == SUCCESS
460 | }
461 |
462 | def "test can't patch pre-built python"() {
463 | given:
464 | settingsFile << "rootProject.name = 'gradle-python-envs-test'"
465 | buildFile << """
466 | plugins {
467 | id "com.jetbrains.python.envs"
468 | }
469 |
470 | apply plugin: 'com.jetbrains.python.envs'
471 |
472 | envs {
473 | bootstrapDirectory = new File(buildDir, 'bootstrap')
474 | envsDirectory = file(buildDir)
475 |
476 | zipRepository = new URL("https://example.com/repo/")
477 | shouldUseZipsFromRepository = true
478 |
479 | python "python-3.10.0", "3.10.0", "64", [], "example.patch"
480 | }
481 | """
482 |
483 | when:
484 | GradleRunner.create()
485 | .withProjectDir(testProjectDir.toFile())
486 | .withArguments('build_envs')
487 | .withPluginClasspath()
488 | .build()
489 |
490 | then:
491 | def e = thrown(Exception.class)
492 | e.message.contains("A patch is defined for a pre-built Python")
493 | }
494 |
495 | def "test apply patch"() {
496 | given:
497 | def patchContents ="""Index: README.rst
498 | IDEA additional info:
499 | Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
500 | <+>UTF-8
501 | ===================================================================
502 | diff --git a/README.rst b/README.rst
503 | --- a/README.rst\t(revision 4641afef661e6a22bc64194bd334b161c95edfe2)
504 | +++ b/README.rst\t(date 1634932856418)
505 | @@ -265,3 +265,5 @@
506 | code but these are entirely optional.
507 |
508 | All trademarks referenced herein are property of their respective holders.
509 | +
510 | +hello, world
511 | \\ No newline at end of file
512 |
513 | """
514 | File patchFile = testProjectDir.newFile("example.patch")
515 | patchFile << patchContents
516 | settingsFile << "rootProject.name = 'gradle-python-envs-test'"
517 | buildFile << """
518 | plugins {
519 | id "com.jetbrains.python.envs"
520 | }
521 |
522 | apply plugin: 'com.jetbrains.python.envs'
523 |
524 | envs {
525 | bootstrapDirectory = new File(buildDir, 'bootstrap')
526 | envsDirectory = file(buildDir)
527 |
528 | python "python-3.10.0", "3.10.0", "64", [], "${patchFile.toURI()}"
529 | }
530 | """
531 |
532 | when:
533 | BuildResult result = GradleRunner.create()
534 | .withProjectDir(testProjectDir.toFile())
535 | .withArguments('build_envs')
536 | .withPluginClasspath()
537 | .build()
538 |
539 | then:
540 | println(result.output)
541 | result.output.contains("patching file README.rst")
542 | result.output.contains('BUILD SUCCESSFUL')
543 | result.task(":build_envs").outcome == SUCCESS
544 | }
545 | }
546 |
--------------------------------------------------------------------------------