diff --git a/.gitignore b/.gitignore index f5e623aa..20182870 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ out/ pivy-importer/ivy-repo/ examples/example-project/activate examples/example-project/pinned.txt +.DS_Store diff --git a/build.gradle b/build.gradle index d7db0452..df933afa 100644 --- a/build.gradle +++ b/build.gradle @@ -23,6 +23,7 @@ subprojects { description = 'Download all dependencies to help with CI cacheing' group = 'CI' doLast { + //noinspection GroovyAssignabilityCheck configurations.each { conf -> conf.files } diff --git a/docs/developers.md b/docs/developers.md index fc7c0b4f..7fa72945 100644 --- a/docs/developers.md +++ b/docs/developers.md @@ -18,6 +18,16 @@ test and add `mavenLocal()` to the repositories. This will configure your project to look in `~/.m2` in addition to other repositories you have configured when pulling artifacts. +If you are building on Windows, PyGradle will avoid using your system temporary +folder for integration tests and instead create and use the folder `c:\tmp`. This +is to avoid issues with Windows's 260 character path limit. Make sure your user +account has the ablity to create and use this folder or your integration tests will fail. + +If you are using Windows 10, it is possible to go beyond that 260 character limit, +but not with this product. That policy change only applies to managed applications. +PyGradle's unit tests are not managed applications. + + ## Contributing To contribute to PyGradle please fork the project, make your changes locally, diff --git a/docs/plugins/python.md b/docs/plugins/python.md index 3aa50c44..d2b342c2 100644 --- a/docs/plugins/python.md +++ b/docs/plugins/python.md @@ -12,6 +12,7 @@ The `com.linkedin.python` plugin adds an extension called `python` to the projec setupCfg = project.file('src').path // The location of this project's setup.cfg file. pinnedFile = project.file('pinned.txt') // A file generated by the build for pip install to consume in no pygradle builds pythonEnvironment = [:] // Environment variables that will be passed to python commands. This will overwrite any existing env variables. + pipConfig = [:] // A Map of Maps to build a pip configuration file in your venv details { virtualEnvPrompt = "(${project.name})" @@ -29,3 +30,35 @@ plugins { id 'com.linkedin.python', version } ``` + +## pipConfig Examples +Sometimes you need to configure pip inside of the venv itself. The most common use cases are when you don't use +pypi.python.com, but you use your own internal pypi server, or you need to set a proxy. Typically, you can configure +this with an operating system +level pip.* file as documented on the [pip web site](https://pip.pypa.io/en/stable/user_guide/#config-file). But +occasionally you need to configure this on a per-project basis. This is where this setting comes in. Take this example + +``` +python{ + pipConfig = ['global':['index-url': 'https://:@your.repo.com/custom/url', 'timeout': '60']] +} +``` + +This is a map of maps. The above will result in a pip.conf (or pip.ini on windows) of + +``` +[global] +index-url = https://:@your.repo.com/custom/url +timeout = 60 +``` + +All of the standard pip configurations will work, this is simply a way of generating the file in the venv + +## Conditional Task Disabling +PyGradle will automatically disable tasks if certain conditions are present. + +* Product install will abort if no `projectRoot/setup.py` file is present +* pytest will skip if the configured `python.testDir` doesn't exist +* coverage will skip if the configured `python.testDir` doesn't exist +* flake8 will skip if both `python.testDir` and `python.srcDir` doesn't exist +* both Sphinx Documentation tasks will skip if the configured `python.docsDir` doesn't exist diff --git a/pygradle-plugin/build.gradle b/pygradle-plugin/build.gradle index 717f42c1..68f216a3 100644 --- a/pygradle-plugin/build.gradle +++ b/pygradle-plugin/build.gradle @@ -59,7 +59,7 @@ task integTest(type: Test) { classpath = sourceSets.integTest.runtimeClasspath reports.html.destination = file("$buildDir${File.separatorChar}reports${File.separatorChar}integration-test") - environment['TEST_REPO'] = rootProject.buildDir.toString().replace('\\', '/') + '/ivy-repo' + environment['TEST_REPO'] = rootProject.buildDir.toURI().toURL().toString() + '/ivy-repo' } check.dependsOn integTest @@ -68,6 +68,7 @@ gradlePlugin { pluginSourceSet project.sourceSets.main testSourceSets project.sourceSets.integTest + //noinspection GroovyAssignabilityCheck plugins { pythonPlugin { id = 'com.linkedin.python' @@ -107,6 +108,7 @@ pluginBundle { description = 'Building Python with Gradle' tags = ['python'] + //noinspection GroovyAssignabilityCheck plugins { pythonPlugin { id = 'com.linkedin.python' diff --git a/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/CleanLeaveVenvTest.groovy b/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/CleanLeaveVenvTest.groovy new file mode 100644 index 00000000..3a270cca --- /dev/null +++ b/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/CleanLeaveVenvTest.groovy @@ -0,0 +1,62 @@ +/* + * Copyright 2016 LinkedIn Corp. + * + * 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 com.linkedin.gradle.python.plugin + +import com.linkedin.gradle.python.plugin.testutils.DefaultBlankProjectLayoutRule +import com.linkedin.gradle.python.plugin.testutils.PyGradleTestBuilder +import com.linkedin.gradle.python.util.StandardTextValues +import org.gradle.testkit.runner.GradleRunner +import org.junit.Rule +import spock.lang.Specification + +/** + * This test class is designed to test scenarios where we are only using pygradle for documentation and + * other scenarios where it may be included in the project, but not active. + */ +class CleanLeaveVenvTest extends Specification { + + @Rule + final DefaultBlankProjectLayoutRule testProjectDir = new DefaultBlankProjectLayoutRule() + + def "Cleans everything from build except venv folder"() { + + given: + testProjectDir.buildFile << """ + | plugins { + | id 'com.linkedin.python' + | } + | ${PyGradleTestBuilder.createRepoClosure()} + """.stripMargin().stripIndent() + + def fvenv = testProjectDir.newFolder(testProjectDir.PROJECT_NAME_DIR, "build", "venv") + def ftest2 = testProjectDir.newFolder(testProjectDir.PROJECT_NAME_DIR, "build", "test2") + def ftest1 = testProjectDir.newFile("${testProjectDir.PROJECT_NAME_DIR + "/build"}/test1.txt") + + when: + def result = GradleRunner.create() + .withProjectDir(testProjectDir.root) + .withArguments(StandardTextValues.TASK_CLEAN_SAVE_VENV.value) + .withPluginClasspath() + .withDebug(true) + .build() + println result.output + + then: "make sure it passes initially first" + !ftest1.exists() + !ftest2.exists() + fvenv.exists() + } +} diff --git a/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/DefaultProjectLayoutRule.groovy b/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/DefaultProjectLayoutRule.groovy deleted file mode 100644 index 92414a94..00000000 --- a/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/DefaultProjectLayoutRule.groovy +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2016 LinkedIn Corp. - * - * 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 com.linkedin.gradle.python.plugin - -import org.junit.rules.TemporaryFolder - -import java.nio.file.Paths - -class DefaultProjectLayoutRule extends TemporaryFolder { - - public File buildFile - - protected void before() throws Throwable { - super.before() - setupProject() - buildFile = newFile(Paths.get('foo', "build.gradle").toString()) - } - - @SuppressWarnings("UnnecessaryOverridingMethod") //docs for testing - protected void after() { - //It's useful to comment this out if you need to look at the test env - super.after() - } - - private void setupProject() { - newFolder('foo', 'src', 'foo') - newFolder('foo', 'test') - - // Create some code - newFile(Paths.get('foo', 'src', 'foo', '__init__.py').toString()) - newFile(Paths.get('foo', 'src', 'foo', 'hello.py').toString()) << '''\ - | from __future__ import print_function - | - | - | def generate_welcome(): - | return 'Hello World' - | - | - | def main(): - | print(generate_welcome()) - |'''.stripMargin().stripIndent() - - // set up the default project name - def settingsGradle = newFile('settings.gradle') - settingsGradle << PyGradleTestBuilder.createSettingGradle() - settingsGradle << "\ninclude ':foo'\n" - - // Create a setup file - newFile(Paths.get('foo', 'setup.py').toString()) << PyGradleTestBuilder.createSetupPy() - - // Create the setup.cfg file - newFile(Paths.get('foo', 'setup.cfg').toString()) << PyGradleTestBuilder.createSetupCfg() - - // Create the test directory and a simple test - newFile(Paths.get('foo', 'test', 'test_a.py').toString()) << '''\ - | from foo.hello import generate_welcome - | - | - | def test_sanity(): - | expected = 6 - | assert 2 * 3 == expected - | - | - | def test_calling_method(): - | assert generate_welcome() == 'Hello World' - '''.stripMargin().stripIndent() - - newFile(Paths.get('foo', "MANIFEST.in").toString()) << '' - } -} diff --git a/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/PexIntegrationTest.groovy b/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/PexIntegrationTest.groovy index 0ea0b591..3dc41268 100644 --- a/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/PexIntegrationTest.groovy +++ b/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/PexIntegrationTest.groovy @@ -15,6 +15,9 @@ */ package com.linkedin.gradle.python.plugin +import com.linkedin.gradle.python.plugin.testutils.DefaultProjectLayoutRule +import com.linkedin.gradle.python.plugin.testutils.ExecUtils +import com.linkedin.gradle.python.plugin.testutils.PyGradleTestBuilder import com.linkedin.gradle.python.util.OperatingSystem import com.linkedin.gradle.python.util.PexFileUtil import org.gradle.testkit.runner.GradleRunner @@ -30,7 +33,6 @@ class PexIntegrationTest extends Specification { @Rule final DefaultProjectLayoutRule testProjectDir = new DefaultProjectLayoutRule() - @IgnoreIf({ OperatingSystem.current() == OperatingSystem.WINDOWS }) def "can build thin pex"() { testProjectDir @@ -51,7 +53,7 @@ class PexIntegrationTest extends Specification { when: def result = GradleRunner.create() .withProjectDir(testProjectDir.root) - .withArguments('build', '--stacktrace') + .withArguments('build', '--stacktrace', '--info') .withPluginClasspath() .withDebug(true) .build() @@ -95,7 +97,7 @@ class PexIntegrationTest extends Specification { |plugins { | id 'com.linkedin.python-pex' |} - | + |version = "1.0.0" |python { | pex { | fatPex = true diff --git a/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/PipIntegrationTest.groovy b/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/PipIntegrationTest.groovy index 63172aa0..810dac16 100644 --- a/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/PipIntegrationTest.groovy +++ b/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/PipIntegrationTest.groovy @@ -15,6 +15,8 @@ */ package com.linkedin.gradle.python.plugin +import com.linkedin.gradle.python.plugin.testutils.DefaultProjectLayoutRule +import com.linkedin.gradle.python.plugin.testutils.PyGradleTestBuilder import org.gradle.testkit.runner.GradleRunner import org.gradle.testkit.runner.TaskOutcome import org.junit.Rule @@ -22,12 +24,10 @@ import spock.lang.Specification import java.nio.file.Paths - class PipIntegrationTest extends Specification { @Rule final DefaultProjectLayoutRule testProjectDir = new DefaultProjectLayoutRule() - def "will write out pinned.txt"() { given: testProjectDir.buildFile << """\ diff --git a/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/PyGradleWithSlimProjectsTest.groovy b/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/PyGradleWithSlimProjectsTest.groovy new file mode 100644 index 00000000..aada90b4 --- /dev/null +++ b/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/PyGradleWithSlimProjectsTest.groovy @@ -0,0 +1,164 @@ +/* + * Copyright 2016 LinkedIn Corp. + * + * 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 com.linkedin.gradle.python.plugin + +import com.linkedin.gradle.python.plugin.testutils.DefaultBlankProjectLayoutRule +import com.linkedin.gradle.python.plugin.testutils.PyGradleTestBuilder +import com.linkedin.gradle.python.util.OperatingSystem +import com.linkedin.gradle.python.util.StandardTextValues +import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome +import org.junit.Rule +import spock.lang.Specification + +import java.nio.file.Paths + +/** + * This test class is designed to test scenarios where we are only using pygradle for documentation and + * other scenarios where it may be included in the project, but not active. + */ +class PyGradleWithSlimProjectsTest extends Specification { + + @Rule + final DefaultBlankProjectLayoutRule testProjectDir = new DefaultBlankProjectLayoutRule() + + def setup() { + + } + + def "verify works with blank project with pipconfig"() { + given: + testProjectDir.buildFile << """ + | plugins { + | id 'com.linkedin.python' + | } + | python{ + | pipConfig = ['global':['index-url': 'https://:@your.repo.com/custom/url', 'timeout': '60']] + | } + | + | ${PyGradleTestBuilder.createRepoClosure()} + """.stripMargin().stripIndent() + + def fileExtension + + if (OperatingSystem.current().isWindows()) { + fileExtension = "ini" + } else { + fileExtension = "conf" + } + + File fpip = Paths.get(testProjectDir.root.absolutePath, testProjectDir.PROJECT_NAME_DIR, "build", "venv", "pip.${fileExtension}").toFile() + + when: + def result = GradleRunner.create() + .withProjectDir(testProjectDir.root) + .withArguments('build', 'coverage', '-s') + .withPluginClasspath() + .withDebug(true) + .build() + println result.output + + then: "make sure it passes initially first" + // there was nothing to build, so it reports up to date + result.task(":${testProjectDir.PROJECT_NAME_DIR}:build").outcome == TaskOutcome.UP_TO_DATE + result.output.contains("BUILD SUCCESS") + + // make sure the pip file is there with the correct contents + fpip.exists() + def lines = fpip.readLines() + lines.get(0) == "[global]" + lines.get(1) == "index-url = https://:@your.repo.com/custom/url" + lines.get(2) == "timeout = 60" + + // "Build will skip things that it should" + result.task(":${testProjectDir.PROJECT_NAME_DIR}:flake8").outcome == TaskOutcome.SKIPPED + result.output.contains("[SKIPPING]") + + // "coverage should be skipped because lack of tests should disable it" + result.task(":${testProjectDir.PROJECT_NAME_DIR}:coverage").outcome == TaskOutcome.SKIPPED + + // "install should be aborted because there is no setup.py" + result.output.contains("[ABORTED]") + result.output.contains("setup.py missing, skipping venv install for product") + + when: "you run the thing again, the venv should be reused and the docs should skip" + result = GradleRunner.create() + .withProjectDir(testProjectDir.root) + .withArguments('buildDocsHtml') + .withPluginClasspath() + .withDebug(true) + .build() + println result.output + + then: "make sure it passes initially first" + result.task(":${testProjectDir.PROJECT_NAME_DIR}:buildDocsHtml").outcome == TaskOutcome.SKIPPED + //result.output.contains("Sphinx docs dir doesn't exist. Aborting") + } + + def "verify docs only nothing else"() { + given: + testProjectDir.buildFile << """ + | plugins { + | id 'com.linkedin.python' + | } + | python{ + | pipConfig = ['global':['index-url': 'https://:@your.repo.com/custom/url', 'timeout': '60']] + | } + | ${PyGradleTestBuilder.createRepoClosure()} + """.stripMargin().stripIndent() + + testProjectDir.copyBuildDocsInfo() + + when: + def result = GradleRunner.create() + .withProjectDir(testProjectDir.root) + .withArguments('buildDocsHtml') + .withPluginClasspath() + .withDebug(true) + .build() + println result.output + + then: "make sure it passes initially first" + result.task(":${testProjectDir.PROJECT_NAME_DIR}:buildDocsHtml").outcome == TaskOutcome.SUCCESS + result.output.contains("Running Sphinx v1.4.1") + result.output.contains("building [html]: targets for 1 source files that are out of date") + result.output.contains("copying static files") + result.output.contains("dumping object inventory") + } + + def "verify pytest and coverage skip when nothing there"() { + given: + testProjectDir.buildFile << """ + | plugins { + | id 'com.linkedin.python' + | } + | ${PyGradleTestBuilder.createRepoClosure()} + """.stripMargin().stripIndent() + + when: + def result = GradleRunner.create() + .withProjectDir(testProjectDir.root) + .withArguments(StandardTextValues.TASK_PYTEST.value, StandardTextValues.TASK_COVERAGE.value) + .withPluginClasspath() + .withDebug(true) + .build() + println result.output + + then: "make sure it passes initially first" + result.task(":${testProjectDir.PROJECT_NAME_DIR}:${StandardTextValues.TASK_PYTEST.value}").outcome == TaskOutcome.SKIPPED + result.task(":${testProjectDir.PROJECT_NAME_DIR}:${StandardTextValues.TASK_COVERAGE.value}").outcome == TaskOutcome.SKIPPED + } +} diff --git a/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/PythonPluginIntegrationTest.groovy b/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/PythonPluginIntegrationTest.groovy index f574552a..ac630f49 100644 --- a/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/PythonPluginIntegrationTest.groovy +++ b/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/PythonPluginIntegrationTest.groovy @@ -15,6 +15,8 @@ */ package com.linkedin.gradle.python.plugin +import com.linkedin.gradle.python.plugin.testutils.DefaultProjectLayoutRule +import com.linkedin.gradle.python.plugin.testutils.PyGradleTestBuilder import org.gradle.testkit.runner.GradleRunner import org.gradle.testkit.runner.TaskOutcome import org.junit.Rule @@ -24,7 +26,6 @@ class PythonPluginIntegrationTest extends Specification { @Rule final DefaultProjectLayoutRule testProjectDir = new DefaultProjectLayoutRule() - def "can build library"() { given: testProjectDir.buildFile << """ diff --git a/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/PythonWebApplicationPluginIntegrationTest.groovy b/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/PythonWebApplicationPluginIntegrationTest.groovy index 646bf9f2..4cc80b88 100644 --- a/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/PythonWebApplicationPluginIntegrationTest.groovy +++ b/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/PythonWebApplicationPluginIntegrationTest.groovy @@ -15,6 +15,9 @@ */ package com.linkedin.gradle.python.plugin +import com.linkedin.gradle.python.plugin.testutils.DefaultProjectLayoutRule +import com.linkedin.gradle.python.plugin.testutils.ExecUtils +import com.linkedin.gradle.python.plugin.testutils.PyGradleTestBuilder import com.linkedin.gradle.python.util.PexFileUtil import org.gradle.testkit.runner.GradleRunner import org.gradle.testkit.runner.TaskOutcome @@ -28,7 +31,6 @@ class PythonWebApplicationPluginIntegrationTest extends Specification { @Rule final DefaultProjectLayoutRule testProjectDir = new DefaultProjectLayoutRule() - def "can build web-app"() { given: testProjectDir.buildFile << """\ diff --git a/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/testutils/DefaultBlankProjectLayoutRule.groovy b/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/testutils/DefaultBlankProjectLayoutRule.groovy new file mode 100644 index 00000000..b4ac5153 --- /dev/null +++ b/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/testutils/DefaultBlankProjectLayoutRule.groovy @@ -0,0 +1,88 @@ +/* + * Copyright 2016 LinkedIn Corp. + * + * 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 com.linkedin.gradle.python.plugin.testutils + +import java.nio.file.Paths + +class DefaultBlankProjectLayoutRule extends DefaultProjectLayoutRule { + + @Override + void setupProject() { + // set up the default project name + def settingsGradle = newFile('settings.gradle') + settingsGradle << PyGradleTestBuilder.createSettingGradle() + settingsGradle << "\ninclude ':foo'\n" + } + + + void copyBuildDocsInfo() { + String docsFldr = "docs" + + newFolder(PROJECT_NAME_DIR, docsFldr) + def spinxConfig = newFile(Paths.get(PROJECT_NAME_DIR, docsFldr, "conf.py").toFile().path) + spinxConfig << sphinxConfigData() + + def indexRs = newFile(Paths.get(PROJECT_NAME_DIR, docsFldr, "index.rst").toFile().path) + + indexRs << """ + This Is Cool + ============ + """.stripIndent() + } + + @SuppressWarnings("GrMethodMayBeStatic") + String sphinxConfigData() { + """ + | import os + | + | extensions = [ + | 'sphinx.ext.autodoc', + | 'sphinx.ext.doctest', + | 'sphinx.ext.intersphinx', + | 'sphinx.ext.todo', + | 'sphinx.ext.coverage', + | 'sphinx.ext.mathjax', + | 'sphinx.ext.ifconfig', + | 'sphinx.ext.viewcode', + | ] + | + | templates_path = ['_templates'] + | source_suffix = '.rst' + | master_doc = 'index' + | project =os.getenv('PYGRADLE_PROJECT_NAME') + | copyright = u'2017, PygradleTests' + | version = os.getenv('PYGRADLE_PROJECT_VERSION') + | release = version + | language = None + | exclude_patterns = ['_build'] + | pygments_style = 'sphinx' + | keep_warnings = True + | todo_include_todos = True + | html_theme = 'bizstyle' + | html_theme_options = {} + | html_static_path = ['_static'] + | htmlhelp_basename = '@htmlhelp_basename@' + | latex_elements = { + | } + | latex_documents = [ + | (master_doc, '@projectName@.tex', u'@projectName@ Documentation', + | u'@author@', 'manual'), + | ] + | epub_title = project + | epub_copyright = copyright + | epub_exclude_files = ['search.html']""".stripMargin().stripIndent() + } +} diff --git a/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/testutils/DefaultProjectLayoutRule.groovy b/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/testutils/DefaultProjectLayoutRule.groovy new file mode 100644 index 00000000..20b36e36 --- /dev/null +++ b/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/testutils/DefaultProjectLayoutRule.groovy @@ -0,0 +1,147 @@ +/* + * Copyright 2016 LinkedIn Corp. + * + * 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 com.linkedin.gradle.python.plugin.testutils + +import com.linkedin.gradle.python.util.OperatingSystem +import org.junit.rules.ExternalResource +import org.junit.rules.TemporaryFolder + +import java.nio.file.Paths + +class DefaultProjectLayoutRule extends ExternalResource implements ProjectLayoutRule { + TemporaryFolder tempFolder + + public static final String PROJECT_NAME_DIR = "foo" + + public DefaultProjectLayoutRule() { + tempFolder = new TemporaryFolder(buildCreateWinTemp()) + } + + @SuppressWarnings("GrMethodMayBeStatic") + private File buildCreateWinTemp() { + if (OperatingSystem.current().isWindows()) { + File tmpFldr = new File("c:/tmp") + if (!tmpFldr.exists()) { + tmpFldr.mkdirs() + } + return tmpFldr + } + return null + } + + public File buildFile + + @Override + void before() throws Throwable { + tempFolder.before() + newFolder(PROJECT_NAME_DIR) + setupProject() + buildFile = newFile(Paths.get(PROJECT_NAME_DIR, "build.gradle").toString()) + } + + @Override + @SuppressWarnings("UnnecessaryOverridingMethod") + void after() { + //It's useful to comment this out if you need to look at the test env + tempFolder.after() + } + + @Override + void create() throws IOException { + tempFolder.create() + } + + @Override + File newFile(String fileName) throws IOException { + tempFolder.newFile(fileName) + } + + @Override + File newFile() throws IOException { + tempFolder.newFile() + } + + @Override + File newFolder(String folder) throws IOException { + tempFolder.newFolder(folder) + } + + @Override + File newFolder(String... folderNames) throws IOException { + tempFolder.newFolder(folderNames) + } + + @Override + File newFolder() throws IOException { + tempFolder.newFolder() + } + + @Override + File getRoot() { + tempFolder.getRoot() + } + + @Override + void delete() { + tempFolder.delete() + } + + void setupProject() { + newFolder(PROJECT_NAME_DIR, 'src', PROJECT_NAME_DIR) + newFolder(PROJECT_NAME_DIR, 'test') + + // Create some code + newFile(Paths.get(PROJECT_NAME_DIR, 'src', PROJECT_NAME_DIR, '__init__.py').toString()) + newFile(Paths.get(PROJECT_NAME_DIR, 'src', PROJECT_NAME_DIR, 'hello.py').toString()) << '''\ + | from __future__ import print_function + | + | + | def generate_welcome(): + | return 'Hello World' + | + | + | def main(): + | print(generate_welcome()) + |'''.stripMargin().stripIndent() + + // set up the default project name + def settingsGradle = newFile('settings.gradle') + settingsGradle << PyGradleTestBuilder.createSettingGradle() + settingsGradle << "\ninclude ':foo'\n" + + // Create a setup file + newFile(Paths.get(PROJECT_NAME_DIR, 'setup.py').toString()) << PyGradleTestBuilder.createSetupPy() + + // Create the setup.cfg file + newFile(Paths.get(PROJECT_NAME_DIR, 'setup.cfg').toString()) << PyGradleTestBuilder.createSetupCfg() + + // Create the test directory and a simple test + newFile(Paths.get(PROJECT_NAME_DIR, 'test', 'test_a.py').toString()) << '''\ + | from foo.hello import generate_welcome + | + | + | def test_sanity(): + | expected = 6 + | assert 2 * 3 == expected + | + | + | def test_calling_method(): + | assert generate_welcome() == 'Hello World' + '''.stripMargin().stripIndent() + + newFile(Paths.get(PROJECT_NAME_DIR, "MANIFEST.in").toString()) << '' + } +} diff --git a/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/ExecUtils.groovy b/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/testutils/ExecUtils.groovy similarity index 96% rename from pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/ExecUtils.groovy rename to pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/testutils/ExecUtils.groovy index 2983677a..75a59d60 100644 --- a/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/ExecUtils.groovy +++ b/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/testutils/ExecUtils.groovy @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.linkedin.gradle.python.plugin +package com.linkedin.gradle.python.plugin.testutils import com.linkedin.gradle.python.util.OperatingSystem import org.apache.commons.exec.CommandLine diff --git a/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/testutils/ProjectLayoutRule.groovy b/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/testutils/ProjectLayoutRule.groovy new file mode 100644 index 00000000..2fa8d3d5 --- /dev/null +++ b/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/testutils/ProjectLayoutRule.groovy @@ -0,0 +1,29 @@ +/* + * Copyright 2016 LinkedIn Corp. + * + * 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 com.linkedin.gradle.python.plugin.testutils + +trait ProjectLayoutRule { + abstract void before() throws Throwable + abstract void after() + abstract void create() throws IOException + abstract File newFile(String fileName) throws IOException + abstract File newFile() throws IOException + abstract File newFolder(String folder) throws IOException + abstract File newFolder(String... folderNames) throws IOException + abstract File newFolder() throws IOException + abstract File getRoot() + abstract void delete() +} diff --git a/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/PyGradleTestBuilder.groovy b/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/testutils/PyGradleTestBuilder.groovy similarity index 78% rename from pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/PyGradleTestBuilder.groovy rename to pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/testutils/PyGradleTestBuilder.groovy index ffac9357..4ed10b57 100644 --- a/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/PyGradleTestBuilder.groovy +++ b/pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/testutils/PyGradleTestBuilder.groovy @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.linkedin.gradle.python.plugin +package com.linkedin.gradle.python.plugin.testutils class PyGradleTestBuilder { @@ -38,12 +38,26 @@ class PyGradleTestBuilder { |""".stripMargin().stripIndent() } + /** + * A convenience method that uses the uri path that is set in the build.gradle file for the ivy location + * + * @return + */ static String createRepoClosure() { + createRepoClosure(System.getenv('TEST_REPO')) + } + + /** + * Creates a local repo block for a test + * @param pathURL path url to use as the location of the ivy project + * @return + */ + static String createRepoClosure(String pathURL) { return """\ |repositories { | ivy { | name 'pypi-local' - | url "file:///${System.getenv('TEST_REPO')}" + url "${pathURL}" | layout "pattern", { | ivy "[organisation]/[module]/[revision]/[module]-[revision].ivy" | artifact "[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier]).[ext]" diff --git a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/PythonExtension.groovy b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/PythonExtension.groovy index 1512eced..bd3dea48 100644 --- a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/PythonExtension.groovy +++ b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/PythonExtension.groovy @@ -59,24 +59,28 @@ class PythonExtension { /** The name of the pinned requirements file. */ public File pinnedFile + /** Settings that can be put into the pip.conf file in the venv */ + public Map> pipConfig = [:] + + /** A way to define forced versions of libraries */ public Map> forcedVersions = [ - 'appdirs' : ['group': 'pypi', 'name': 'appdirs', 'version': '1.4.0'], - 'argparse' : ['group': 'pypi', 'name': 'argparse', 'version': '1.4.0'], - 'flake8' : ['group': 'pypi', 'name': 'flake8', 'version': '2.5.4'], - 'packaging' : ['group': 'pypi', 'name': 'packaging', 'version': '16.8'], - 'pbr' : ['group': 'pypi', 'name': 'pbr', 'version': '1.8.0'], - 'pex' : ['group': 'pypi', 'name': 'pex', 'version': '1.1.4'], - 'pip' : ['group': 'pypi', 'name': 'pip', 'version': '7.1.2'], - 'pytest' : ['group': 'pypi', 'name': 'pytest', 'version': '2.9.1'], - 'pytest-cov' : ['group': 'pypi', 'name': 'pytest-cov', 'version': '2.2.1'], - 'pytest-xdist' : ['group': 'pypi', 'name': 'pytest-xdist', 'version': '1.14'], - 'setuptools' : ['group': 'pypi', 'name': 'setuptools', 'version': '19.1.1'], - 'setuptools-git': ['group': 'pypi', 'name': 'setuptools-git', 'version': '1.1'], - 'six' : ['group': 'pypi', 'name': 'six', 'version': '1.10.0'], - 'Sphinx' : ['group': 'pypi', 'name': 'Sphinx', 'version': '1.4.1'], - 'virtualenv' : ['group': 'pypi', 'name': 'virtualenv', 'version': '15.0.1'], - 'wheel' : ['group': 'pypi', 'name': 'wheel', 'version': '0.26.0'], + 'appdirs' : ['group': 'pypi', 'name': 'appdirs', 'version': '1.4.0'], + 'argparse' : ['group': 'pypi', 'name': 'argparse', 'version': '1.4.0'], + 'flake8' : ['group': 'pypi', 'name': 'flake8', 'version': '2.5.4'], + 'packaging' : ['group': 'pypi', 'name': 'packaging', 'version': '16.8'], + 'pbr' : ['group': 'pypi', 'name': 'pbr', 'version': '1.8.0'], + 'pex' : ['group': 'pypi', 'name': 'pex', 'version': '1.1.4'], + 'pip' : ['group': 'pypi', 'name': 'pip', 'version': '9.0.1'], + 'pytest' : ['group': 'pypi', 'name': 'pytest', 'version': '2.9.1'], + 'pytest-cov' : ['group': 'pypi', 'name': 'pytest-cov', 'version': '2.2.1'], + 'pytest-xdist' : ['group': 'pypi', 'name': 'pytest-xdist', 'version': '1.14'], + 'setuptools' : ['group': 'pypi', 'name': 'setuptools', 'version': '19.1.1'], + 'setuptools-git': ['group': 'pypi', 'name': 'setuptools-git', 'version': '1.1'], + 'six' : ['group': 'pypi', 'name': 'six', 'version': '1.10.0'], + 'Sphinx' : ['group': 'pypi', 'name': 'Sphinx', 'version': '1.4.1'], + 'virtualenv' : ['group': 'pypi', 'name': 'virtualenv', 'version': '15.0.1'], + 'wheel' : ['group': 'pypi', 'name': 'wheel', 'version': '0.26.0'], ] /* Container of the details related to the venv/python instance */ @@ -84,12 +88,13 @@ class PythonExtension { public ConsoleOutput consoleOutput = ConsoleOutput.ASCII - public PythonExtension(Project project) { + PythonExtension(Project project) { this.details = new PythonDetails(project) docsDir = Paths.get(project.projectDir.absolutePath, "docs").toFile().path testDir = Paths.get(project.projectDir.absolutePath, "test").toFile().path srcDir = Paths.get(project.projectDir.absolutePath, "src").toFile().path setupCfg = Paths.get(project.projectDir.absolutePath, "setup.cfg").toFile().path + pinnedFile = project.file("pinned.txt") def applicationDirectory = VirtualEnvironment.getPythonApplicationDirectory() diff --git a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/checkstyle/model/FileStyleViolationsContainer.java b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/checkstyle/model/FileStyleViolationsContainer.java index 4f1387f2..0bdaf565 100644 --- a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/checkstyle/model/FileStyleViolationsContainer.java +++ b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/checkstyle/model/FileStyleViolationsContainer.java @@ -15,15 +15,15 @@ */ package com.linkedin.gradle.python.checkstyle.model; +import org.gradle.api.logging.Logger; +import org.gradle.api.logging.Logging; + import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.gradle.api.logging.Logger; -import org.gradle.api.logging.Logging; - /** * This will contain all of the violations, handling adding violations to existing files, and adding new files. diff --git a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/extension/DeployableExtension.java b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/extension/DeployableExtension.java index a0da4e66..88704341 100644 --- a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/extension/DeployableExtension.java +++ b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/extension/DeployableExtension.java @@ -15,10 +15,10 @@ */ package com.linkedin.gradle.python.extension; -import java.io.File; - import org.gradle.api.Project; +import java.io.File; + public class DeployableExtension { diff --git a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/extension/PexExtension.java b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/extension/PexExtension.java index 66a9bb26..adfd6cd7 100644 --- a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/extension/PexExtension.java +++ b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/extension/PexExtension.java @@ -15,11 +15,11 @@ */ package com.linkedin.gradle.python.extension; -import java.io.File; - import com.linkedin.gradle.python.util.OperatingSystem; import org.gradle.api.Project; +import java.io.File; + public class PexExtension { diff --git a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/extension/WheelExtension.java b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/extension/WheelExtension.java index 88e659b1..dce1e370 100644 --- a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/extension/WheelExtension.java +++ b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/extension/WheelExtension.java @@ -15,9 +15,10 @@ */ package com.linkedin.gradle.python.extension; -import java.io.File; import org.gradle.api.Project; +import java.io.File; + public class WheelExtension { diff --git a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/plugin/PythonCliDistributionPlugin.java b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/plugin/PythonCliDistributionPlugin.java index 3ef17ac5..a38820b7 100644 --- a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/plugin/PythonCliDistributionPlugin.java +++ b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/plugin/PythonCliDistributionPlugin.java @@ -17,6 +17,7 @@ import com.linkedin.gradle.python.tasks.GenerateCompletionsTask; import com.linkedin.gradle.python.util.ExtensionUtils; +import com.linkedin.gradle.python.util.StandardTextValues; import org.gradle.api.Project; @@ -30,7 +31,7 @@ public void applyTo(Project project) { ExtensionUtils.maybeCreateCliExtension(project); GenerateCompletionsTask completionsTask = project.getTasks().create(TASK_GENERATE_COMPLETIONS, GenerateCompletionsTask.class); - completionsTask.dependsOn(project.getTasks().getByName(PythonPlugin.TASK_INSTALL_PROJECT)); + completionsTask.dependsOn(project.getTasks().getByName(StandardTextValues.TASK_INSTALL_PROJECT.getValue())); project.getTasks().getByName(PythonPexDistributionPlugin.TASK_BUILD_PEX).dependsOn(project.getTasks().getByName(TASK_GENERATE_COMPLETIONS)); } diff --git a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/plugin/PythonFlyerPlugin.groovy b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/plugin/PythonFlyerPlugin.groovy index a8fa8f36..7738a5e1 100644 --- a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/plugin/PythonFlyerPlugin.groovy +++ b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/plugin/PythonFlyerPlugin.groovy @@ -17,6 +17,7 @@ package com.linkedin.gradle.python.plugin import com.linkedin.gradle.python.util.ExtensionUtils import com.linkedin.gradle.python.util.FileSystemUtils +import com.linkedin.gradle.python.util.StandardTextValues import groovy.transform.CompileStatic import org.gradle.api.Plugin import org.gradle.api.Project @@ -88,7 +89,7 @@ class PythonFlyerPlugin implements Plugin { } } - project.tasks.getByName(PythonPlugin.TASK_INSTALL_PROJECT).dependsOn(project.tasks.getByName(TASK_SETUP_RESOURCE_LINK)) + project.tasks.getByName(StandardTextValues.TASK_INSTALL_PROJECT.value).dependsOn(project.tasks.getByName(TASK_SETUP_RESOURCE_LINK)) /* diff --git a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/plugin/PythonPexDistributionPlugin.groovy b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/plugin/PythonPexDistributionPlugin.groovy index eb82560c..89b2c418 100644 --- a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/plugin/PythonPexDistributionPlugin.groovy +++ b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/plugin/PythonPexDistributionPlugin.groovy @@ -19,6 +19,7 @@ import com.linkedin.gradle.python.extension.DeployableExtension import com.linkedin.gradle.python.tasks.BuildPexTask import com.linkedin.gradle.python.tasks.BuildWheelsTask import com.linkedin.gradle.python.util.ExtensionUtils +import com.linkedin.gradle.python.util.StandardTextValues import org.gradle.api.Action import org.gradle.api.Project import org.gradle.api.tasks.bundling.Compression @@ -42,10 +43,10 @@ class PythonPexDistributionPlugin extends PythonBasePlugin { project.afterEvaluate { if (settings.details.pythonVersion.pythonMajorMinor == '2.6') { - project.dependencies.add(PythonPlugin.CONFIGURATION_BUILD_REQS, extension.forcedVersions['argparse']) + project.dependencies.add(StandardTextValues.CONFIGURATION_BUILD_REQS.value, extension.forcedVersions['argparse']) } } - project.dependencies.add(PythonPlugin.CONFIGURATION_BUILD_REQS, extension.forcedVersions['pex']) + project.dependencies.add(StandardTextValues.CONFIGURATION_BUILD_REQS.value, extension.forcedVersions['pex']) /** @@ -54,7 +55,7 @@ class PythonPexDistributionPlugin extends PythonBasePlugin { * We need wheels to build pex files. */ project.tasks.create(TASK_BUILD_WHEELS, BuildWheelsTask) { task -> - task.dependsOn project.tasks.getByName(PythonPlugin.TASK_INSTALL_PROJECT) + task.dependsOn project.tasks.getByName(StandardTextValues.TASK_INSTALL_PROJECT.value) } project.tasks.create(TASK_BUILD_PEX, BuildPexTask) { task -> @@ -72,6 +73,6 @@ class PythonPexDistributionPlugin extends PythonBasePlugin { }) packageDeployable.dependsOn(project.tasks.getByName(TASK_BUILD_PEX)) - project.artifacts.add(PythonPlugin.CONFIGURATION_DEFAULT, packageDeployable) + project.artifacts.add(StandardTextValues.CONFIGURATION_DEFAULT.value, packageDeployable) } } diff --git a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/plugin/PythonPlugin.groovy b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/plugin/PythonPlugin.groovy index 032a94b4..903d5020 100644 --- a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/plugin/PythonPlugin.groovy +++ b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/plugin/PythonPlugin.groovy @@ -27,40 +27,12 @@ import org.gradle.api.artifacts.repositories.IvyPatternRepositoryLayout import org.gradle.api.tasks.bundling.Compression import org.gradle.api.tasks.bundling.Tar +import static com.linkedin.gradle.python.util.StandardTextValues.* + @SuppressWarnings("AbcMetric") //TODO: Break apart method class PythonPlugin implements Plugin { - public final static String CONFIGURATION_BOOTSTRAP_REQS = 'pygradleBootstrap' - public final static String CONFIGURATION_SETUP_REQS = 'setupRequires' - public final static String CONFIGURATION_BUILD_REQS = 'build' - public final static String CONFIGURATION_DEFAULT = 'default' - public final static String CONFIGURATION_PYDOCS = 'pydocs' - public final static String CONFIGURATION_PYTHON = 'python' - public final static String CONFIGURATION_TEST = 'test' - public final static String CONFIGURATION_VENV = 'venv' - public final static String CONFIGURATION_WHEEL = 'wheel' - - public final static String TASK_BUILD_DOCS = 'buildDocs' - public final static String TASK_CHECK = 'check' - public final static String TASK_COVERAGE = 'coverage' - public final static String TASK_FLAKE = 'flake8' - public final static String TASK_CHECKSTYLE = 'flake8Checkstyle' - public final static String TASK_INSTALL_SETUP_REQS = 'installSetupRequirements' - public final static String TASK_INSTALL_BUILD_REQS = 'installBuildRequirements' - public final static String TASK_INSTALL_PROJECT = 'installProject' - public final static String TASK_INSTALL_PYTHON_REQS = 'installPythonRequirements' - public final static String TASK_INSTALL_TEST_REQS = 'installTestRequirements' - public final static String TASK_PACKAGE_DOCS = 'packageDocs' - public final static String TASK_PACKAGE_JSON_DOCS = 'packageJsonDocs' - public final static String TASK_PYTEST = 'pytest' - public final static String TASK_SETUP_LINKS = 'installLinks' - public final static String TASK_VENV_CREATE = 'createVirtualEnvironment' - public final static String TASK_PIN_REQUIREMENTS = 'pinRequirements' - public final static String TASK_SETUP_PY_WRITER = 'generateSetupPy' - - public final static String DOCUMENTATION_GROUP = 'documentation' - @Override @SuppressWarnings(["MethodSize", "AbcMetric"]) //TODO: Break apart method @@ -70,7 +42,7 @@ class PythonPlugin implements Plugin { project.plugins.apply('base') - def pythonConf = project.configurations.create(CONFIGURATION_PYTHON) + def pythonConf = project.configurations.create(CONFIGURATION_PYTHON.value) /* * To resolve transitive dependencies, we need the 'default' configuration * to extend the 'python' configuration. This is because the source @@ -79,13 +51,13 @@ class PythonPlugin implements Plugin { */ project.configurations.'default'.extendsFrom(pythonConf) - project.configurations.create(CONFIGURATION_BOOTSTRAP_REQS) - project.configurations.create(CONFIGURATION_SETUP_REQS) - project.configurations.create(CONFIGURATION_BUILD_REQS) - project.configurations.create(CONFIGURATION_PYDOCS) - project.configurations.create(CONFIGURATION_TEST) - project.configurations.create(CONFIGURATION_VENV) - project.configurations.create(CONFIGURATION_WHEEL) + project.configurations.create(CONFIGURATION_BOOTSTRAP_REQS.value) + project.configurations.create(CONFIGURATION_SETUP_REQS.value) + project.configurations.create(CONFIGURATION_BUILD_REQS.value) + project.configurations.create(CONFIGURATION_PYDOCS.value) + project.configurations.create(CONFIGURATION_TEST.value) + project.configurations.create(CONFIGURATION_VENV.value) + project.configurations.create(CONFIGURATION_WHEEL.value) /* * We must extend test configuration from the python configuration. @@ -105,22 +77,22 @@ class PythonPlugin implements Plugin { * highest version of setuptools used. Provide the dependencies in the * best order they should be installed in setupRequires configuration. */ - project.dependencies.add(CONFIGURATION_BOOTSTRAP_REQS, settings.forcedVersions['virtualenv']) + project.dependencies.add(CONFIGURATION_BOOTSTRAP_REQS.value, settings.forcedVersions['virtualenv']) - project.dependencies.add(CONFIGURATION_SETUP_REQS, settings.forcedVersions['appdirs']) - project.dependencies.add(CONFIGURATION_SETUP_REQS, settings.forcedVersions['packaging']) - project.dependencies.add(CONFIGURATION_SETUP_REQS, settings.forcedVersions['wheel']) - project.dependencies.add(CONFIGURATION_SETUP_REQS, settings.forcedVersions['setuptools']) - project.dependencies.add(CONFIGURATION_SETUP_REQS, settings.forcedVersions['pip']) - project.dependencies.add(CONFIGURATION_SETUP_REQS, settings.forcedVersions['setuptools-git']) - project.dependencies.add(CONFIGURATION_SETUP_REQS, settings.forcedVersions['pbr']) + project.dependencies.add(CONFIGURATION_SETUP_REQS.value, settings.forcedVersions['appdirs']) + project.dependencies.add(CONFIGURATION_SETUP_REQS.value, settings.forcedVersions['packaging']) + project.dependencies.add(CONFIGURATION_SETUP_REQS.value, settings.forcedVersions['wheel']) + project.dependencies.add(CONFIGURATION_SETUP_REQS.value, settings.forcedVersions['setuptools']) + project.dependencies.add(CONFIGURATION_SETUP_REQS.value, settings.forcedVersions['pip']) + project.dependencies.add(CONFIGURATION_SETUP_REQS.value, settings.forcedVersions['setuptools-git']) + project.dependencies.add(CONFIGURATION_SETUP_REQS.value, settings.forcedVersions['pbr']) - project.dependencies.add(CONFIGURATION_BUILD_REQS, settings.forcedVersions['flake8']) - project.dependencies.add(CONFIGURATION_BUILD_REQS, settings.forcedVersions['Sphinx']) + project.dependencies.add(CONFIGURATION_BUILD_REQS.value, settings.forcedVersions['flake8']) + project.dependencies.add(CONFIGURATION_BUILD_REQS.value, settings.forcedVersions['Sphinx']) - project.dependencies.add(CONFIGURATION_TEST, settings.forcedVersions['pytest']) - project.dependencies.add(CONFIGURATION_TEST, settings.forcedVersions['pytest-cov']) - project.dependencies.add(CONFIGURATION_TEST, settings.forcedVersions['pytest-xdist']) + project.dependencies.add(CONFIGURATION_TEST.value, settings.forcedVersions['pytest']) + project.dependencies.add(CONFIGURATION_TEST.value, settings.forcedVersions['pytest-cov']) + project.dependencies.add(CONFIGURATION_TEST.value, settings.forcedVersions['pytest-xdist']) /* * To prevent base dependencies, such as setuptools, from installing/reinstalling, we will @@ -128,6 +100,7 @@ class PythonPlugin implements Plugin { * good versions that satisfy all the requirements. */ def dependencyResolveDetails = new PyGradleDependencyResolveDetails(settings.forcedVersions) + //noinspection GroovyAssignabilityCheck project.configurations.each { configuration -> configuration.resolutionStrategy.eachDependency(dependencyResolveDetails) } @@ -135,7 +108,7 @@ class PythonPlugin implements Plugin { /* * Write the direct dependencies into a requirements file as a list of pinned versions. */ - def pinRequirementsTask = project.tasks.create(TASK_PIN_REQUIREMENTS, PinRequirementsTask) + def pinRequirementsTask = project.tasks.create(TASK_PIN_REQUIREMENTS.value, PinRequirementsTask) /** * Install virtualenv. @@ -143,7 +116,7 @@ class PythonPlugin implements Plugin { * Install the virtualenv version that we implicitly depend on so that we * can run on systems that don't have virtualenv already installed. */ - project.tasks.create(TASK_VENV_CREATE, InstallVirtualEnvironmentTask) { task -> + project.tasks.create(TASK_VENV_CREATE.value, InstallVirtualEnvironmentTask) { task -> task.dependsOn pinRequirementsTask task.pythonDetails = settings.details } @@ -151,8 +124,8 @@ class PythonPlugin implements Plugin { /** * Creates a link so users can activate into the virtual environment. */ - project.tasks.create(TASK_SETUP_LINKS) { - dependsOn project.tasks.getByName(TASK_VENV_CREATE) + project.tasks.create(TASK_SETUP_LINKS.value) { + dependsOn project.tasks.getByName(TASK_VENV_CREATE.value) outputs.file(settings.getDetails().activateLink) doLast { @@ -162,15 +135,21 @@ class PythonPlugin implements Plugin { } } + /** + * task that cleans the project but leaves the venv in tact. Helpful for projects on windows that + * take a very long time to build the venv. + */ + project.tasks.create([name: TASK_CLEAN_SAVE_VENV, type: CleanSaveVenvTask, group: BUILD_GROUP]) + /** * Install core setup requirements into virtualenv. * * Core setup requirements are the packages we need to run Python build for this product. * They need to be installed in a specific order. Hence, we set sorted to false here. */ - project.tasks.create(TASK_INSTALL_SETUP_REQS, PipInstallTask) { + project.tasks.create(TASK_INSTALL_SETUP_REQS.value, PipInstallTask) { pythonDetails = settings.details - dependsOn project.tasks.getByName(TASK_SETUP_LINKS) + dependsOn project.tasks.getByName(TASK_SETUP_LINKS.value) args = ['--upgrade'] installFileCollection = project.configurations.setupRequires sorted = false @@ -181,9 +160,9 @@ class PythonPlugin implements Plugin { * * The build requirements are the packages we need to run all stages of the build for this product. */ - project.tasks.create(TASK_INSTALL_BUILD_REQS, PipInstallTask) { + project.tasks.create(TASK_INSTALL_BUILD_REQS.value, PipInstallTask) { pythonDetails = settings.details - dependsOn project.tasks.getByName(TASK_INSTALL_SETUP_REQS) + dependsOn project.tasks.getByName(TASK_INSTALL_SETUP_REQS.value) args = ['--upgrade'] installFileCollection = project.configurations.build } @@ -194,9 +173,9 @@ class PythonPlugin implements Plugin { * A products Python requirements are those listed in the product-spec.json in the external or product sections. * */ - project.tasks.create(TASK_INSTALL_PYTHON_REQS, PipInstallTask) { + project.tasks.create(TASK_INSTALL_PYTHON_REQS.value, PipInstallTask) { pythonDetails = settings.details - dependsOn project.tasks.getByName(TASK_INSTALL_BUILD_REQS) + dependsOn project.tasks.getByName(TASK_INSTALL_BUILD_REQS.value) installFileCollection = project.configurations.python } @@ -205,9 +184,9 @@ class PythonPlugin implements Plugin { * * A products test requirements are those that are listed in the ``test`` configuration and are only required for running tests. */ - project.tasks.create(TASK_INSTALL_TEST_REQS, PipInstallTask) { + project.tasks.create(TASK_INSTALL_TEST_REQS.value, PipInstallTask) { pythonDetails = settings.details - dependsOn project.tasks.getByName(TASK_INSTALL_PYTHON_REQS) + dependsOn project.tasks.getByName(TASK_INSTALL_PYTHON_REQS.value) installFileCollection = project.configurations.test } @@ -216,9 +195,9 @@ class PythonPlugin implements Plugin { * * This installs the product itself in editable mode. It is equivalent to running ``python setup.py develop`` on a Python product. */ - project.tasks.create(TASK_INSTALL_PROJECT, PipInstallTask) { + project.tasks.create(TASK_INSTALL_PROJECT.value, PipInstallTask) { pythonDetails = settings.details - dependsOn project.tasks.getByName(TASK_INSTALL_TEST_REQS) + dependsOn project.tasks.getByName(TASK_INSTALL_TEST_REQS.value) installFileCollection = project.files(project.file(project.projectDir)) args = ['--editable'] environment = settings.pythonEnvironmentDistgradle @@ -228,14 +207,14 @@ class PythonPlugin implements Plugin { * Any task that extends from {@link AbstractPythonMainSourceDefaultTask} will require TASK_INSTALL_BUILD_REQS */ project.tasks.withType(AbstractPythonMainSourceDefaultTask) { Task task -> - task.dependsOn project.tasks.getByName(TASK_INSTALL_BUILD_REQS) + task.dependsOn project.tasks.getByName(TASK_INSTALL_BUILD_REQS.value) } /** * Any task that extends from {@link AbstractPythonTestSourceDefaultTask} will require TASK_INSTALL_PROJECT */ project.tasks.withType(AbstractPythonTestSourceDefaultTask) { Task task -> - task.dependsOn project.tasks.getByName(TASK_INSTALL_PROJECT) + task.dependsOn project.tasks.getByName(TASK_INSTALL_PROJECT.value) } /** @@ -243,74 +222,95 @@ class PythonPlugin implements Plugin { * * This uses the ``setup.cfg`` if present to configure py.test. */ - project.tasks.create(TASK_PYTEST, PyTestTask) + def pyTestTask = project.tasks.create(TASK_PYTEST.value, PyTestTask) + + pyTestTask.onlyIf { + project.file(project.python.testDir).exists() + } // Add a dependency to task ``check`` to depend on our Python plugin's ``pytest`` task - project.tasks.getByName(TASK_CHECK).dependsOn(project.tasks.getByName(TASK_PYTEST)) + project.tasks.getByName(TASK_CHECK.value).dependsOn(project.tasks.getByName(TASK_PYTEST.value)) /** * Run coverage using py.test. * * This uses the ``setup.cfg`` if present to configure py.test. */ - project.tasks.create(TASK_COVERAGE, PyCoverageTask) + def pyCheckTask = project.tasks.create(TASK_COVERAGE.value, PyCoverageTask) + pyCheckTask.onlyIf { + project.file(project.python.testDir).exists() + } /** * Run flake8. * * This uses the ``setup.cfg`` if present to configure flake8. */ - project.tasks.create(TASK_FLAKE, Flake8Task) + def flakeTask = project.tasks.create(TASK_FLAKE.value, Flake8Task) + + flakeTask.onlyIf { + Integer paths = 0 + if (project.file(project.python.srcDir).exists()) { ++paths } + if (project.file(project.python.testDir).exists()) { ++paths } + + paths > 0 + } /** * Create checkstyle styled report from flake */ - project.tasks.create(TASK_CHECKSTYLE, CheckStyleGeneratorTask) + project.tasks.create(TASK_CHECKSTYLE.value, CheckStyleGeneratorTask) // Add a dependency to task ``check`` to depend on our Python plugin's ``flake8`` task - project.tasks.getByName(TASK_CHECK).dependsOn(project.tasks.getByName(TASK_FLAKE)) + project.tasks.getByName(TASK_CHECK.value).dependsOn(project.tasks.getByName(TASK_FLAKE.value)) /** * Create documentation using Sphinx. */ - def sphinxHtml = project.tasks.create(TASK_BUILD_DOCS + 'Html', SphinxDocumentationTask) { + def sphinxHtml = project.tasks.create(TASK_BUILD_DOCS.value + 'Html', SphinxDocumentationTask) { type = SphinxDocumentationTask.DocType.HTML - dependsOn TASK_INSTALL_PROJECT + dependsOn TASK_INSTALL_PROJECT.value + } + sphinxHtml.onlyIf { + project.file(project.python.docsDir).exists() } - def sphinxJson = project.tasks.create(TASK_BUILD_DOCS + 'Json', SphinxDocumentationTask) { + def sphinxJson = project.tasks.create(TASK_BUILD_DOCS.value + 'Json', SphinxDocumentationTask) { type = SphinxDocumentationTask.DocType.JSON - dependsOn TASK_INSTALL_PROJECT + dependsOn TASK_INSTALL_PROJECT.value + } + sphinxJson.onlyIf { + project.file(project.python.docsDir).exists() } project.tasks.withType(SphinxDocumentationTask).each { SphinxDocumentationTask task -> - task.group = DOCUMENTATION_GROUP + task.group = DOCUMENTATION_GROUP.value task.description = "Generate Sphinx documentation in ${task.type.builderName} format" } - project.tasks.create(TASK_BUILD_DOCS) { + project.tasks.create(TASK_BUILD_DOCS.value) { dependsOn sphinxHtml, sphinxJson } /** * Tar up the documentation. */ - def packageDocsTask = project.tasks.create(TASK_PACKAGE_DOCS, Tar, new PackageDocumentationAction(sphinxHtml)) - packageDocsTask.dependsOn(project.tasks.getByName(TASK_BUILD_DOCS)) + def packageDocsTask = project.tasks.create(TASK_PACKAGE_DOCS.value, Tar, new PackageDocumentationAction(sphinxHtml)) + packageDocsTask.dependsOn(project.tasks.getByName(TASK_BUILD_DOCS.value)) /** * Tar up the JSON documentation. */ - def packageJsonDocsTask = project.tasks.create(TASK_PACKAGE_JSON_DOCS, Tar, new PackageDocumentationAction(sphinxJson)) - packageJsonDocsTask.dependsOn(project.tasks.getByName(TASK_BUILD_DOCS)) + def packageJsonDocsTask = project.tasks.create(TASK_PACKAGE_JSON_DOCS.value, Tar, new PackageDocumentationAction(sphinxJson)) + packageJsonDocsTask.dependsOn(project.tasks.getByName(TASK_BUILD_DOCS.value)) // Only build the artifact if the docs source directory actually exists if (project.file(settings.docsDir).exists()) { - project.artifacts.add(CONFIGURATION_PYDOCS, packageDocsTask) - project.artifacts.add(CONFIGURATION_PYDOCS, packageJsonDocsTask) + project.artifacts.add(CONFIGURATION_PYDOCS.value, packageDocsTask) + project.artifacts.add(CONFIGURATION_PYDOCS.value, packageJsonDocsTask) } - project.tasks.create(TASK_SETUP_PY_WRITER, GenerateSetupPyTask) + project.tasks.create(TASK_SETUP_PY_WRITER.value, GenerateSetupPyTask) project.getRepositories().metaClass.pyGradlePyPi = { -> delegate.ivy(new Action() { diff --git a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/plugin/PythonSourceDistributionPlugin.groovy b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/plugin/PythonSourceDistributionPlugin.groovy index e2412287..2306eb2b 100644 --- a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/plugin/PythonSourceDistributionPlugin.groovy +++ b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/plugin/PythonSourceDistributionPlugin.groovy @@ -17,6 +17,7 @@ package com.linkedin.gradle.python.plugin import com.linkedin.gradle.python.tasks.SourceDistTask +import com.linkedin.gradle.python.util.StandardTextValues import org.gradle.api.Project class PythonSourceDistributionPlugin extends PythonBasePlugin { @@ -30,7 +31,7 @@ class PythonSourceDistributionPlugin extends PythonBasePlugin { * Create a Python source distribution. */ def sdistPackageTask = project.tasks.create(TASK_PACKAGE_SDIST, SourceDistTask) { - dependsOn(project.tasks.getByName(PythonPlugin.TASK_INSTALL_PROJECT)) + dependsOn(project.tasks.getByName(StandardTextValues.TASK_INSTALL_PROJECT.value)) } def sdistArtifactInfo = [ @@ -41,7 +42,7 @@ class PythonSourceDistributionPlugin extends PythonBasePlugin { builtBy: project.tasks.getByName(TASK_PACKAGE_SDIST), ] - project.artifacts.add(PythonPlugin.CONFIGURATION_DEFAULT, sdistArtifactInfo) + project.artifacts.add(StandardTextValues.CONFIGURATION_DEFAULT.value, sdistArtifactInfo) } } diff --git a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/plugin/PythonWebApplicationPlugin.groovy b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/plugin/PythonWebApplicationPlugin.groovy index bb21e585..be3d6cec 100644 --- a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/plugin/PythonWebApplicationPlugin.groovy +++ b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/plugin/PythonWebApplicationPlugin.groovy @@ -20,6 +20,7 @@ import com.linkedin.gradle.python.extension.PexExtension import com.linkedin.gradle.python.extension.WheelExtension import com.linkedin.gradle.python.tasks.BuildWebAppTask import com.linkedin.gradle.python.util.ExtensionUtils +import com.linkedin.gradle.python.util.StandardTextValues import org.gradle.api.Action import org.gradle.api.Project import org.gradle.api.tasks.bundling.Compression @@ -71,7 +72,7 @@ class PythonWebApplicationPlugin extends PythonBasePlugin { }) packageDeployable.dependsOn(project.tasks.getByName(TASK_BUILD_WEB_APPLICATION)) - project.artifacts.add(PythonPlugin.CONFIGURATION_DEFAULT, packageDeployable) + project.artifacts.add(StandardTextValues.CONFIGURATION_DEFAULT.value, packageDeployable) } } diff --git a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/plugin/PythonWheelDistributionPlugin.groovy b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/plugin/PythonWheelDistributionPlugin.groovy index 88757ee3..eb15b8da 100644 --- a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/plugin/PythonWheelDistributionPlugin.groovy +++ b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/plugin/PythonWheelDistributionPlugin.groovy @@ -15,6 +15,7 @@ */ package com.linkedin.gradle.python.plugin +import com.linkedin.gradle.python.util.StandardTextValues import org.gradle.api.Project @@ -32,7 +33,7 @@ class PythonWheelDistributionPlugin extends PythonBasePlugin { * Create a Python wheel distribution. */ project.tasks.create(TASK_PACKAGE_WHEEL) { - dependsOn(project.tasks.getByName(PythonPlugin.TASK_INSTALL_PROJECT)) + dependsOn(project.tasks.getByName(StandardTextValues.TASK_INSTALL_PROJECT.value)) outputs.file(wheelArtifact) doLast { project.exec { @@ -49,7 +50,7 @@ class PythonWheelDistributionPlugin extends PythonBasePlugin { builtBy: project.tasks.getByName(TASK_PACKAGE_WHEEL), ] if (!project.spec.version.contains('SNAPSHOT')) { - project.artifacts.add(PythonPlugin.CONFIGURATION_WHEEL, wheelArtifactInfo) + project.artifacts.add(StandardTextValues.CONFIGURATION_WHEEL.value, wheelArtifactInfo) } } diff --git a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/CleanSaveVenvTask.java b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/CleanSaveVenvTask.java new file mode 100644 index 00000000..900d005c --- /dev/null +++ b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/CleanSaveVenvTask.java @@ -0,0 +1,38 @@ +/* + * Copyright 2016 LinkedIn Corp. + * + * 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 com.linkedin.gradle.python.tasks; + +import org.gradle.api.DefaultTask; +import org.gradle.api.tasks.TaskAction; + +import java.io.File; + + +public class CleanSaveVenvTask extends DefaultTask { + + @TaskAction + public void cleanProject() throws Exception { + File buildDir = getProject().getBuildDir(); + File[] directoryListing = buildDir.listFiles(); + if (directoryListing != null) { + for (File f : directoryListing) { + if (!f.getName().equals("venv")) { + f.delete(); + } + } + } + } +} diff --git a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/Flake8Task.groovy b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/Flake8Task.groovy index 8707a3ba..bc81d5a9 100644 --- a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/Flake8Task.groovy +++ b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/Flake8Task.groovy @@ -21,14 +21,44 @@ import org.gradle.process.ExecResult @CompileStatic public class Flake8Task extends AbstractPythonMainSourceDefaultTask { - public void preExecution() { - args(pythonDetails.virtualEnvironment.findExecutable("flake8").absolutePath, - "--config", "$component.setupCfg", - "$component.srcDir", - "$component.testDir") - } - - @Override - void processResults(ExecResult execResult) { - } + public void preExecution() { + /* + Modified to only include folders that exist. if no folders exist, then + the task isn't actually run. + */ + List sArgs = [pythonDetails.virtualEnvironment.findExecutable("flake8").absolutePath, + "--config", component.setupCfg] + + def paths = [] + if (project.file(component.srcDir).exists()) { + project.logger.info("Flake8: adding ${component.srcDir}") + paths.add(component.srcDir) + } else { + project.logger.info("Flake8: srcDir doesn't exist") + } + + if (project.file(component.testDir).exists()) { + project.logger.info("Flake8: adding ${component.testDir}") + paths.add(component.testDir) + } else { + project.logger.info("Flake8: testDir doesn't exist") + } + + // creating a flake8 config file if one doesn't exist, this prevents "file not found" issues. + def cfgCheck = project.file(component.setupCfg) + if (!cfgCheck.exists()) { + project.logger.info("Flake8 config file doesn't exist, creating default") + cfgCheck.createNewFile() + cfgCheck << "[flake8]" + } else { + project.logger.info("Flake8 config file exists") + } + + sArgs.addAll(paths) + args(sArgs) + } + + @Override + void processResults(ExecResult execResult) { + } } diff --git a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/InstallVirtualEnvironmentTask.groovy b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/InstallVirtualEnvironmentTask.groovy index 48f85387..2f2be77e 100644 --- a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/InstallVirtualEnvironmentTask.groovy +++ b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/InstallVirtualEnvironmentTask.groovy @@ -18,6 +18,7 @@ package com.linkedin.gradle.python.tasks import com.linkedin.gradle.python.extension.PythonDetails import com.linkedin.gradle.python.tasks.execution.FailureReasonProvider import com.linkedin.gradle.python.tasks.execution.TeeOutputContainer +import com.linkedin.gradle.python.util.pip.PipConfFile import groovy.transform.CompileDynamic import groovy.transform.CompileStatic import org.gradle.api.Action @@ -55,6 +56,8 @@ class InstallVirtualEnvironmentTask extends DefaultTask implements FailureReason @CompileDynamic void installVEnv() { + PipConfFile pipConfFile = new PipConfFile(project, pythonDetails) + File packageDir = new File(project.buildDir, "virtualenv-dir") if (packageDir.exists()) { project.delete(packageDir) @@ -63,7 +66,6 @@ class InstallVirtualEnvironmentTask extends DefaultTask implements FailureReason packageDir.mkdirs() getPyGradleBootstrap().files.each { file -> - project.copy { from project.tarTree(file.path) into packageDir @@ -84,6 +86,7 @@ class InstallVirtualEnvironmentTask extends DefaultTask implements FailureReason } }) project.delete(packageDir) + pipConfFile.buildPipConfFile() } private String findVirtualEnvDependencyVersion() { diff --git a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/PinRequirementsTask.java b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/PinRequirementsTask.java index a412fbd8..4b2f9e78 100644 --- a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/PinRequirementsTask.java +++ b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/PinRequirementsTask.java @@ -16,8 +16,8 @@ package com.linkedin.gradle.python.tasks; import com.linkedin.gradle.python.PythonExtension; -import com.linkedin.gradle.python.plugin.PythonPlugin; import com.linkedin.gradle.python.util.ExtensionUtils; +import com.linkedin.gradle.python.util.StandardTextValues; import org.apache.commons.io.FileUtils; import org.gradle.api.DefaultTask; import org.gradle.api.artifacts.Configuration; @@ -62,7 +62,7 @@ public void writeOutPinnedFile() throws IOException { public Configuration getPythonConfiguration() { return getProject() .getConfigurations() - .getByName(PythonPlugin.CONFIGURATION_PYTHON); + .getByName(StandardTextValues.CONFIGURATION_PYTHON.getValue()); } @OutputFile diff --git a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/PipInstallTask.groovy b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/PipInstallTask.groovy index 7b4e41d1..2615cfdb 100644 --- a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/PipInstallTask.groovy +++ b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/PipInstallTask.groovy @@ -86,6 +86,29 @@ class PipInstallTask extends DefaultTask implements FailureReasonProvider { return sorted ? installFileCollection.files.sort() : installFileCollection.files } + /** + * Method that checks to ensure that the current project is prepared to pip install. It ignores the + * base pygradle libraries + * + * @param installable file object for the system to install + * @return + */ + private boolean isReadyForInstall(File installable) { + if (installable.absolutePath == project.projectDir.absolutePath) { + /* + we are installing the product itself. Now lets see if its ready for it + this ignores dependencies + */ + def setupPyFile = new File(installable.absolutePath, "setup.py") + if (!setupPyFile.exists()) { + logger.lifecycle(PythonHelpers.createPrettyLine("Install ${project.name}", "[ABORTED]")) + project.logger.warn("setup.py missing, skipping venv install for product ${project.name}. Run 'gradle generateSetupPy' to generate a generic file") + return false + } + } + return true + } + /** * Install things into a virtual environment. *

@@ -94,80 +117,80 @@ class PipInstallTask extends DefaultTask implements FailureReasonProvider { * express and download dependencies. */ @TaskAction - public void pipInstall() { - + void pipInstall() { def pyVersion = pythonDetails.getPythonVersion().pythonMajorMinor def extension = ExtensionUtils.getPythonExtension(project) def sitePackages = findSitePackages() for (File installable : getConfigurationFiles()) { + if (isReadyForInstall(installable)) { + def packageInfo = PackageInfo.fromPath(installable.getAbsolutePath()) + def shortHand = packageInfo.version ? "${packageInfo.name}-${packageInfo.version}" : packageInfo.name - def packageInfo = PackageInfo.fromPath(installable.getAbsolutePath()) - def shortHand = packageInfo.version ? "${packageInfo.name}-${packageInfo.version}" : packageInfo.name - - if (packageExcludeFilter.isSatisfiedBy(packageInfo)) { - logger.lifecycle(PythonHelpers.createPrettyLine("Install ${shortHand}", "[EXCLUDED]")) - continue - } + if (packageExcludeFilter.isSatisfiedBy(packageInfo)) { + logger.lifecycle(PythonHelpers.createPrettyLine("Install ${shortHand}", "[EXCLUDED]")) + continue + } - String sanitizedName = packageInfo.name.replace('-', '_') + String sanitizedName = packageInfo.name.replace('-', '_') - // See: https://www.python.org/dev/peps/pep-0376/ - File egg = sitePackages.resolve("${ sanitizedName }-${ packageInfo.version }-py${ pyVersion }.egg-info").toFile() - File dist = sitePackages.resolve("${sanitizedName}-${packageInfo.version}.dist-info").toFile() + // See: https://www.python.org/dev/peps/pep-0376/ + File egg = sitePackages.resolve("${sanitizedName}-${packageInfo.version}-py${pyVersion}.egg-info").toFile() + File dist = sitePackages.resolve("${sanitizedName}-${packageInfo.version}.dist-info").toFile() - def mergedEnv = new HashMap(extension.pythonEnvironment) - if (environment != null) { - mergedEnv.putAll(environment) - } + def mergedEnv = new HashMap(extension.pythonEnvironment) + if (environment != null) { + mergedEnv.putAll(environment) + } - def commandLine = [pythonDetails.getVirtualEnvInterpreter(), - pythonDetails.getVirtualEnvironment().getPip(), - 'install', - '--disable-pip-version-check', - '--no-deps'] - commandLine.addAll(args) - commandLine.add(installable.getAbsolutePath()) - - if (project.file(egg).exists() || project.file(dist).exists()) { - logger.lifecycle(PythonHelpers.createPrettyLine("Install ${shortHand}", "[SKIPPING]")) - continue - } + def commandLine = [pythonDetails.getVirtualEnvInterpreter(), + pythonDetails.getVirtualEnvironment().getPip(), + 'install', + '--disable-pip-version-check', + '--no-deps'] + commandLine.addAll(args) + commandLine.add(installable.getAbsolutePath()) + + if (project.file(egg).exists() || project.file(dist).exists()) { + logger.lifecycle(PythonHelpers.createPrettyLine("Install ${shortHand}", "[SKIPPING]")) + continue + } - logger.lifecycle(PythonHelpers.createPrettyLine("Install ${shortHand}", "[STARTING]")) + logger.lifecycle(PythonHelpers.createPrettyLine("Install ${shortHand}", "[STARTING]")) - def startTime = new Date() - def stream = new ByteArrayOutputStream() - ExecResult installResult = project.exec { ExecSpec execSpec -> - execSpec.environment mergedEnv - execSpec.commandLine(commandLine) - execSpec.standardOutput = stream - execSpec.errorOutput = stream - execSpec.ignoreExitValue = true - } - def endTime = new Date() - def duration = TimeCategory.minus(endTime, startTime) - - def message = stream.toString().trim() - if (installResult.exitValue != 0) { - /* - * TODO: maintain a list of packages that failed to install, and report a failure - * report at the end. We can leverage our domain expertise here to provide very - * meaningful errors. E.g., we see lxml failed to install, do you have libxml2 - * installed? E.g., we see pyOpenSSL>0.15 failed to install, do you have libffi - * installed? - */ - logger.lifecycle(message) - lastInstallMessage = message - - throw new PipInstallException( - "Failed to install ${shortHand}. Please see above output for reason, or re-run your build using ``gradle -i build`` for additional logging.") - } else { - if (extension.consoleOutput == ConsoleOutput.RAW) { + def startTime = new Date() + def stream = new ByteArrayOutputStream() + ExecResult installResult = project.exec { ExecSpec execSpec -> + execSpec.environment mergedEnv + execSpec.commandLine(commandLine) + execSpec.standardOutput = stream + execSpec.errorOutput = stream + execSpec.ignoreExitValue = true + } + def endTime = new Date() + def duration = TimeCategory.minus(endTime, startTime) + + def message = stream.toString().trim() + if (installResult.exitValue != 0) { + /* + * TODO: maintain a list of packages that failed to install, and report a failure + * report at the end. We can leverage our domain expertise here to provide very + * meaningful errors. E.g., we see lxml failed to install, do you have libxml2 + * installed? E.g., we see pyOpenSSL>0.15 failed to install, do you have libffi + * installed? + */ logger.lifecycle(message) + lastInstallMessage = message + + throw new PipInstallException( + "Failed to install ${shortHand}. Please see above output for reason, or re-run your build using ``gradle -i build`` for additional logging.") } else { - String prefix = String.format("Install (%d:%02d.%03d s)", duration.minutes, duration.seconds % 60, duration.millis % 1000) - logger.lifecycle(PythonHelpers.createPrettyLine(prefix, "[FINISHED]")) + if (extension.consoleOutput == ConsoleOutput.RAW) { + logger.lifecycle(message) + } else { + String prefix = String.format("Install (%d:%02d.%03d s)", duration.minutes, duration.seconds % 60, duration.millis % 1000) + logger.lifecycle(PythonHelpers.createPrettyLine(prefix, "[FINISHED]")) + } } } } @@ -182,7 +205,7 @@ class PipInstallTask extends DefaultTask implements FailureReasonProvider { private Path findSitePackages() { def pyVersion = pythonDetails.getPythonVersion().pythonMajorMinor if (OperatingSystem.current().isUnix()) { - return pythonDetails.virtualEnv.toPath().resolve(Paths.get("lib", "python${ pyVersion }", "site-packages")) + return pythonDetails.virtualEnv.toPath().resolve(Paths.get("lib", "python${pyVersion}", "site-packages")) } else { return pythonDetails.virtualEnv.toPath().resolve(Paths.get("Lib", "site-packages")) } diff --git a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/PyTestTask.groovy b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/PyTestTask.groovy index 86684cf1..1094d125 100644 --- a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/PyTestTask.groovy +++ b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/PyTestTask.groovy @@ -26,6 +26,7 @@ import org.gradle.process.ExecResult class PyTestTask extends AbstractPythonTestSourceDefaultTask { private static final int NO_TESTS_COLLECTED_ERRNO = 5 + private static final String NO_TEST_WARNING = "***** WARNING: You did not write any tests! *****" /** specific test file was given and only the tests in that file should be executed */ boolean specificFileGiven = false @@ -38,19 +39,19 @@ class PyTestTask extends AbstractPythonTestSourceDefaultTask { @Override public void preExecution() { - args(pythonDetails.virtualEnvironment.findExecutable("py.test").absolutePath) - if (extraArgs != []) { - args(extraArgs) - } - if (!specificFileGiven) { - args(component.testDir) - } + args(pythonDetails.virtualEnvironment.findExecutable("py.test").absolutePath) + if (extraArgs != []) { + args(extraArgs) + } + if (!specificFileGiven) { + args(component.testDir) + } } @Override void processResults(ExecResult execResult) { if (execResult.exitValue == NO_TESTS_COLLECTED_ERRNO) { - logger.warn("***** WARNING: You did not write any tests! *****") + logger.warn(NO_TEST_WARNING) } else { execResult.assertNormalExitValue() } diff --git a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/SourceDistTask.java b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/SourceDistTask.java index 08813158..5e7eb493 100644 --- a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/SourceDistTask.java +++ b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/SourceDistTask.java @@ -15,12 +15,12 @@ */ package com.linkedin.gradle.python.tasks; -import java.io.File; - import org.gradle.api.Project; import org.gradle.api.tasks.OutputFile; import org.gradle.process.ExecResult; +import java.io.File; + public class SourceDistTask extends AbstractPythonMainSourceDefaultTask { @@ -36,10 +36,10 @@ public void processResults(ExecResult execResult) { public File getSdistOutput() { Project project = getProject(); return new File( - getDistDir(), + getDistDir(), String.format( - "%s-%s.tar.gz", - project.getName(), + "%s-%s.tar.gz", + project.getName(), project.getVersion().toString().replace("_", "-"))); } diff --git a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/SphinxDocumentationTask.groovy b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/SphinxDocumentationTask.groovy index b95836d2..d859b105 100644 --- a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/SphinxDocumentationTask.groovy +++ b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/SphinxDocumentationTask.groovy @@ -39,11 +39,11 @@ class SphinxDocumentationTask extends AbstractPythonMainSourceDefaultTask { public DocType type @Override - public void preExecution() { + void preExecution() { args(pythonDetails.virtualEnvironment.findExecutable('sphinx-build').absolutePath, '-b', type.builderName, project.file(component.docsDir).getAbsolutePath(), - "${ getDocDir().getAbsolutePath() }") + "${getDocDir().getAbsolutePath()}") } @Override @@ -53,7 +53,7 @@ class SphinxDocumentationTask extends AbstractPythonMainSourceDefaultTask { /** * Types of documentation that are supported by Sphinx */ - public enum DocType { + enum DocType { JSON('json'), HTML('html') diff --git a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/util/StandardTextValues.groovy b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/util/StandardTextValues.groovy new file mode 100644 index 00000000..f36eb632 --- /dev/null +++ b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/util/StandardTextValues.groovy @@ -0,0 +1,70 @@ +/* + * Copyright 2016 LinkedIn Corp. + * + * 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 com.linkedin.gradle.python.util + +/** + * CodeNarc is complaining that some classes exceed 350 lines. TO fall into compliance with CodeNarc, the standard values + * are being moved to this enum. Its an Enum rather than an interface because according to CodeNarc, interfaces of nothing + * but constants is now taboo. + */ +enum StandardTextValues { + CONFIGURATION_BOOTSTRAP_REQS('pygradleBootstrap'), + CONFIGURATION_SETUP_REQS('setupRequires'), + CONFIGURATION_BUILD_REQS('build'), + CONFIGURATION_DEFAULT('default'), + CONFIGURATION_PYDOCS('pydocs'), + CONFIGURATION_PYTHON('python'), + CONFIGURATION_TEST('test'), + CONFIGURATION_VENV('venv'), + CONFIGURATION_WHEEL('wheel'), + + TASK_BUILD_DOCS('buildDocs'), + TASK_CLEAN_SAVE_VENV('cleanSaveVenv'), + TASK_CHECK('check'), + TASK_COVERAGE('coverage'), + TASK_FLAKE('flake8'), + TASK_CHECKSTYLE('flake8Checkstyle'), + TASK_INSTALL_SETUP_REQS('installSetupRequirements'), + TASK_INSTALL_BUILD_REQS('installBuildRequirements'), + TASK_INSTALL_PROJECT('installProject'), + TASK_INSTALL_PYTHON_REQS('installPythonRequirements'), + TASK_INSTALL_TEST_REQS('installTestRequirements'), + TASK_PACKAGE_DOCS('packageDocs'), + TASK_PACKAGE_JSON_DOCS('packageJsonDocs'), + TASK_PYTEST('pytest'), + TASK_SETUP_LINKS('installLinks'), + TASK_VENV_CREATE('createVirtualEnvironment'), + TASK_PIN_REQUIREMENTS('pinRequirements'), + TASK_SETUP_PY_WRITER('generateSetupPy'), + + DOCUMENTATION_GROUP('documentation'), + BUILD_GROUP('build') + + private final String value + + StandardTextValues(String val) { + this.value = val + } + + @Override + String toString() { + return value + } + + String getValue() { + return value + } +} diff --git a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/util/internal/pex/PipFreezeAction.java b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/util/internal/pex/PipFreezeAction.java index 7a8c34f3..927cd9c4 100644 --- a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/util/internal/pex/PipFreezeAction.java +++ b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/util/internal/pex/PipFreezeAction.java @@ -16,9 +16,9 @@ package com.linkedin.gradle.python.util.internal.pex; import com.linkedin.gradle.python.PythonExtension; -import com.linkedin.gradle.python.plugin.PythonPlugin; import com.linkedin.gradle.python.util.ExtensionUtils; import com.linkedin.gradle.python.util.PackageInfo; +import com.linkedin.gradle.python.util.StandardTextValues; import org.gradle.api.Action; import org.gradle.api.Project; import org.gradle.process.ExecSpec; @@ -43,11 +43,11 @@ public List getDependencies() { final PythonExtension settings = ExtensionUtils.getPythonExtension(project); // Setup requirements, build, and test dependencies - Set developmentDependencies = configurationToSet(project, PythonPlugin.CONFIGURATION_SETUP_REQS); - developmentDependencies.addAll(configurationToSet(project, PythonPlugin.CONFIGURATION_BUILD_REQS)); - developmentDependencies.addAll(configurationToSet(project, PythonPlugin.CONFIGURATION_TEST)); + Set developmentDependencies = configurationToSet(project, StandardTextValues.CONFIGURATION_SETUP_REQS.getValue()); + developmentDependencies.addAll(configurationToSet(project, StandardTextValues.CONFIGURATION_BUILD_REQS.getValue())); + developmentDependencies.addAll(configurationToSet(project, StandardTextValues.CONFIGURATION_TEST.getValue())); - developmentDependencies.removeAll(configurationToSet(project, PythonPlugin.CONFIGURATION_PYTHON)); + developmentDependencies.removeAll(configurationToSet(project, StandardTextValues.CONFIGURATION_PYTHON.getValue())); if (Objects.equals(settings.getDetails().getPythonVersion().getPythonMajorMinor(), "2.6") && developmentDependencies.contains("argparse")) { diff --git a/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/util/pip/PipConfFile.java b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/util/pip/PipConfFile.java new file mode 100644 index 00000000..7629f584 --- /dev/null +++ b/pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/util/pip/PipConfFile.java @@ -0,0 +1,80 @@ +/* + * Copyright 2016 LinkedIn Corp. + * + * 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 com.linkedin.gradle.python.util.pip; + +import com.linkedin.gradle.python.PythonExtension; +import com.linkedin.gradle.python.extension.PythonDetails; +import com.linkedin.gradle.python.util.OperatingSystem; +import org.gradle.api.Project; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Paths; +import java.util.Map; + +public class PipConfFile { + + private Project project; + private PythonDetails pythonDetails; + private String fileExtension; + + public PipConfFile(Project project, PythonDetails pythonDetails) { + this.project = project; + this.pythonDetails = pythonDetails; + + if (OperatingSystem.current().isWindows()) { + fileExtension = "ini"; + } else { + fileExtension = "conf"; + } + + } + + /** + * Writes a new pip.conf file. You can configure the contents of this file with the python extension + * or leave it blank for it to pick up the pip.conf in your system properties. + * + * index-url = https://pypi.python.org/simple/ + * @return + */ + public void buildPipConfFile() throws IOException { + + File pip = Paths.get(pythonDetails.getVirtualEnv().getAbsolutePath(), "pip." + fileExtension).toFile().getAbsoluteFile(); + if (!pip.exists()) { + project.getLogger().info("creating pip." + fileExtension); + pip.createNewFile(); + + PythonExtension pythonExtension = project.getExtensions().getByType(PythonExtension.class); + + Map> pipConfig = pythonExtension.pipConfig; + + if (pipConfig.size() > 0) { + PrintWriter writer = new PrintWriter(pip, "UTF-8"); + + for (Map.Entry> entry : pipConfig.entrySet()) { + writer.println("[" + entry.getKey() + "]"); + + for (Map.Entry entry2 : entry.getValue().entrySet()) { + writer.println(entry2.getKey() + " = " + entry2.getValue()); + } + } + + writer.close(); + } + } + } +} diff --git a/pygradle-plugin/src/main/resources/templates/click_tabtab.py b/pygradle-plugin/src/main/resources/templates/click_tabtab.py index 910b5e08..5c482f95 100755 --- a/pygradle-plugin/src/main/resources/templates/click_tabtab.py +++ b/pygradle-plugin/src/main/resources/templates/click_tabtab.py @@ -21,12 +21,11 @@ import logging import os +import pkg_resources import re import sys import textwrap -import pkg_resources - try: import click except ImportError: diff --git a/pygradle-plugin/src/test/groovy/com/linkedin/gradle/python/plugin/PythonPluginTest.groovy b/pygradle-plugin/src/test/groovy/com/linkedin/gradle/python/plugin/PythonPluginTest.groovy index 69519b9a..423a9953 100644 --- a/pygradle-plugin/src/test/groovy/com/linkedin/gradle/python/plugin/PythonPluginTest.groovy +++ b/pygradle-plugin/src/test/groovy/com/linkedin/gradle/python/plugin/PythonPluginTest.groovy @@ -18,6 +18,7 @@ package com.linkedin.gradle.python.plugin import com.linkedin.gradle.python.PythonExtension import com.linkedin.gradle.python.tasks.PipInstallTask import com.linkedin.gradle.python.util.OperatingSystem +import com.linkedin.gradle.python.util.StandardTextValues import com.linkedin.gradle.python.util.WindowsBinaryUnpacker import org.gradle.testfixtures.ProjectBuilder import org.junit.Rule @@ -58,13 +59,14 @@ class PythonPluginTest extends Specification { def project = new ProjectBuilder().build() project.plugins.apply('com.linkedin.python') then: - for (c in [PythonPlugin.CONFIGURATION_DEFAULT, - PythonPlugin.CONFIGURATION_PYTHON, - PythonPlugin.CONFIGURATION_WHEEL, - PythonPlugin.CONFIGURATION_VENV]) { + for (c in [StandardTextValues.CONFIGURATION_DEFAULT.value, + StandardTextValues.CONFIGURATION_PYTHON.value, + StandardTextValues.CONFIGURATION_WHEEL.value, + StandardTextValues.CONFIGURATION_VENV.value]) { assert project.configurations.getByName(c) } - assert (project.configurations.getByName(PythonPlugin.CONFIGURATION_DEFAULT).getExtendsFrom()*.name == [PythonPlugin.CONFIGURATION_PYTHON]) + assert (project.configurations.getByName(StandardTextValues.CONFIGURATION_DEFAULT.value).getExtendsFrom()*.name + == [StandardTextValues.CONFIGURATION_PYTHON.value]) } def 'can apply java'() { @@ -100,7 +102,7 @@ class PythonPluginTest extends Specification { project.plugins.apply('com.linkedin.python') then: - PipInstallTask install = (PipInstallTask) project.tasks.getByName(PythonPlugin.TASK_INSTALL_PROJECT) + PipInstallTask install = (PipInstallTask) project.tasks.getByName(StandardTextValues.TASK_INSTALL_PROJECT.value) install.installFileCollection.getFiles().size() == 1 install.installFileCollection.getFiles().first().getName() == projectDir.getName() }