├── .circleci └── config.yml ├── .editorconfig ├── .gitignore ├── .release-manager.toml ├── .travis.yml ├── LICENSE ├── NOTICE ├── README.md ├── appveyor.yml ├── build.gradle ├── buildSrc ├── build.gradle └── src │ └── main │ └── java │ └── com │ └── linkedin │ └── gradle │ └── build │ ├── package-info.java │ └── version │ ├── Version.java │ ├── VersionFile.java │ └── VersionPlugin.java ├── codequality ├── HEADER ├── checkstyle │ ├── checkstyle.xml │ └── suppressions.xml ├── codenarc │ └── rules.groovy └── findbugs │ └── findbugsExclude.xml ├── docs ├── changelog.md ├── compatibility.md ├── developers.md ├── etc.md ├── getting-started.md ├── pivy-importer.md └── plugins │ ├── python-cli.md │ ├── python-flyer.md │ ├── python-pex.md │ ├── python-sdist.md │ ├── python-web-app.md │ └── python.md ├── examples ├── example-project │ ├── .gitignore │ ├── README.md │ ├── build.gradle │ ├── settings.gradle │ ├── setup.cfg │ ├── setup.py │ ├── src │ │ └── helloworld │ │ │ └── __init__.py │ └── test │ │ └── test_example.py └── iris-classification │ ├── .gitignore │ ├── README.md │ ├── build.gradle │ ├── settings.gradle │ ├── setup.py │ ├── src │ └── classify_iris │ │ ├── __init__.py │ │ └── run.py │ └── test │ └── test_dataset.py ├── gradle.properties ├── gradle ├── code-quality.gradle ├── publishing.gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── pivy-importer ├── build.gradle └── src │ ├── main │ ├── groovy │ │ └── com │ │ │ └── linkedin │ │ │ └── python │ │ │ └── importer │ │ │ ├── ImporterCLI.java │ │ │ ├── deps │ │ │ ├── DependencyDownloader.groovy │ │ │ ├── DependencySubstitution.groovy │ │ │ ├── SdistDownloader.groovy │ │ │ └── WheelsDownloader.groovy │ │ │ ├── distribution │ │ │ ├── PythonPackage.groovy │ │ │ ├── SourceDistPackage.groovy │ │ │ └── WheelsPackage.groovy │ │ │ ├── ivy │ │ │ └── IvyFileWriter.groovy │ │ │ ├── pypi │ │ │ ├── ProjectDetails.groovy │ │ │ ├── PypiApiCache.groovy │ │ │ ├── VersionEntry.groovy │ │ │ └── VersionRange.groovy │ │ │ └── util │ │ │ └── ProxyDetector.groovy │ └── resources │ │ └── logback.xml │ └── test │ ├── groovy │ └── com │ │ └── linkedin │ │ └── python │ │ └── importer │ │ ├── deps │ │ ├── DependencyDownloaderTest.groovy │ │ └── DependencySubstitutionTest.groovy │ │ ├── distribution │ │ ├── PythonPackageTest.groovy │ │ ├── SourceDistPacakgeTest.groovy │ │ └── WheelsPackageTest.groovy │ │ └── ivy │ │ └── IvyFileWriterTest.groovy │ └── resources │ └── deps │ ├── Django-2.0.6-py3-none-any-METADATA │ ├── Django-2.0.6-py3-none-any.ivy │ ├── Django-2.0.6-py3-none-any.whl │ ├── PKG-INFO │ ├── WMI-1.4.8.zip │ ├── WMI.json │ ├── python-dateutil-2.7.3.tar.gz │ └── pywin32-223-cp27-cp27m-win_amd64.whl ├── pygradle-plugin ├── build.gradle └── src │ ├── integTest │ ├── groovy │ │ └── com │ │ │ └── linkedin │ │ │ └── gradle │ │ │ └── python │ │ │ ├── plugin │ │ │ ├── CleanLeaveVenvTest.groovy │ │ │ ├── FailureReasonProviderIntegrationTest.groovy │ │ │ ├── ParallelWheelsIntegrationTest.groovy │ │ │ ├── PexIntegrationTest.groovy │ │ │ ├── PipIntegrationTest.groovy │ │ │ ├── PyGradleWithSlimProjectsTest.groovy │ │ │ ├── PythonPluginIntegrationTest.groovy │ │ │ ├── PythonSerializableTest.groovy │ │ │ ├── PythonWebApplicationPluginIntegrationTest.groovy │ │ │ └── testutils │ │ │ │ ├── DefaultBlankProjectLayoutRule.groovy │ │ │ │ ├── DefaultProjectLayoutRule.groovy │ │ │ │ ├── ExecUtils.groovy │ │ │ │ ├── ProjectLayoutRule.groovy │ │ │ │ ├── PyGradleTestBuilder.groovy │ │ │ │ └── PythonTemporaryFolder.java │ │ │ └── tasks │ │ │ ├── Flake8TaskIntegrationTest.groovy │ │ │ └── InstallVirtualEnvironmentTaskTest.groovy │ └── resources │ │ └── setup.py │ ├── main │ ├── groovy │ │ └── com │ │ │ └── linkedin │ │ │ └── gradle │ │ │ └── python │ │ │ ├── PythonExtension.groovy │ │ │ ├── checkstyle │ │ │ ├── CheckStyleXmlReporter.java │ │ │ └── model │ │ │ │ ├── FileStyleViolations.java │ │ │ │ ├── FileStyleViolationsContainer.java │ │ │ │ └── StyleViolation.java │ │ │ ├── coverage │ │ │ └── CoverageXmlReporter.groovy │ │ │ ├── exception │ │ │ ├── MissingInterpreterException.java │ │ │ └── PipExecutionException.java │ │ │ ├── extension │ │ │ ├── BlackExtension.java │ │ │ ├── CliExtension.java │ │ │ ├── CoverageExtension.java │ │ │ ├── DeployableExtension.java │ │ │ ├── IsortExtension.java │ │ │ ├── MypyExtension.java │ │ │ ├── PexExtension.java │ │ │ ├── PythonDefaultVersions.java │ │ │ ├── PythonDetails.java │ │ │ ├── PythonDetailsFactory.java │ │ │ ├── PythonVersion.java │ │ │ ├── PythonVersionParser.java │ │ │ ├── VirtualEnvironment.java │ │ │ ├── WheelExtension.java │ │ │ ├── ZipappContainerExtension.java │ │ │ └── internal │ │ │ │ ├── DefaultExternalTool.java │ │ │ │ ├── DefaultPythonDetails.java │ │ │ │ └── DefaultVirtualEnvironment.java │ │ │ ├── plugin │ │ │ ├── LayeredWheelCachePlugin.java │ │ │ ├── PyGradleDependencyResolveDetails.java │ │ │ ├── PythonBasePlugin.java │ │ │ ├── PythonCliDistributionPlugin.java │ │ │ ├── PythonContainerPlugin.java │ │ │ ├── PythonFlyerPlugin.java │ │ │ ├── PythonHelpers.java │ │ │ ├── PythonPexDistributionPlugin.java │ │ │ ├── PythonPlugin.java │ │ │ ├── PythonSourceDistributionPlugin.java │ │ │ ├── PythonWebApplicationPlugin.java │ │ │ ├── PythonWheelDistributionPlugin.java │ │ │ ├── WheelFirstPlugin.java │ │ │ └── internal │ │ │ │ ├── DocumentationPlugin.java │ │ │ │ ├── InstallDependenciesPlugin.java │ │ │ │ └── ValidationPlugin.java │ │ │ ├── tasks │ │ │ ├── AbstractPythonMainSourceDefaultTask.java │ │ │ ├── AbstractPythonTestSourceDefaultTask.java │ │ │ ├── BlackTask.java │ │ │ ├── BuildPexTask.java │ │ │ ├── BuildWebAppTask.java │ │ │ ├── BuildWheelsTask.groovy │ │ │ ├── CheckStyleGeneratorTask.java │ │ │ ├── CleanSaveVenvTask.java │ │ │ ├── Flake8Task.java │ │ │ ├── GenerateCompletionsTask.groovy │ │ │ ├── GenerateSetupPyTask.java │ │ │ ├── GetProbedTagsTask.java │ │ │ ├── InstallVirtualEnvironmentTask.java │ │ │ ├── IsortTask.java │ │ │ ├── LayeredWheelCacheTask.java │ │ │ ├── MypyTask.java │ │ │ ├── NoopBuildPexTask.java │ │ │ ├── NoopTask.java │ │ │ ├── ParallelWheelGenerationTask.java │ │ │ ├── PinRequirementsTask.java │ │ │ ├── PipInstallTask.groovy │ │ │ ├── PyCoverageTask.groovy │ │ │ ├── PyTestTask.groovy │ │ │ ├── PythonContainerTask.java │ │ │ ├── SourceDistTask.java │ │ │ ├── SphinxDocumentationTask.groovy │ │ │ ├── action │ │ │ │ ├── CreateVirtualEnvAction.java │ │ │ │ ├── ProbeVenvInfoAction.java │ │ │ │ ├── VirtualEnvCustomizer.java │ │ │ │ └── pip │ │ │ │ │ ├── AbstractPipAction.java │ │ │ │ │ ├── PipInstallAction.java │ │ │ │ │ ├── PipWheelAction.java │ │ │ │ │ └── WheelBuilder.java │ │ │ ├── exec │ │ │ │ ├── ExternalExec.java │ │ │ │ └── ProjectExternalExec.java │ │ │ ├── execution │ │ │ │ ├── FailureReasonProvider.java │ │ │ │ └── TeeOutputContainer.java │ │ │ ├── provides │ │ │ │ └── ProvidesVenv.java │ │ │ └── supports │ │ │ │ ├── HasPythonDetails.java │ │ │ │ ├── SupportsDistutilsCfg.java │ │ │ │ ├── SupportsLayeredWheelCache.java │ │ │ │ ├── SupportsPackageFiltering.java │ │ │ │ ├── SupportsPackageInfoSettings.java │ │ │ │ └── SupportsWheelCache.java │ │ │ ├── util │ │ │ ├── ApplicationContainer.java │ │ │ ├── DefaultEnvironmentMerger.java │ │ │ ├── DefaultPackageSettings.java │ │ │ ├── DependencyOrder.java │ │ │ ├── EntryPointHelpers.groovy │ │ │ ├── EnvironmentMerger.java │ │ │ ├── ExtensionUtils.java │ │ │ ├── FileSystemUtils.java │ │ │ ├── NoopOutputStream.java │ │ │ ├── OperatingSystem.java │ │ │ ├── PackageInfo.groovy │ │ │ ├── PackageSettings.java │ │ │ ├── PexFileUtil.groovy │ │ │ ├── StandardTextValues.java │ │ │ ├── entrypoint │ │ │ │ └── EntryPointWriter.java │ │ │ ├── internal │ │ │ │ ├── PyPiRepoUtil.groovy │ │ │ │ ├── TaskTimer.java │ │ │ │ ├── pex │ │ │ │ │ ├── FatPexGenerator.java │ │ │ │ │ ├── PexExecOutputParser.java │ │ │ │ │ ├── PexExecSpecAction.java │ │ │ │ │ └── ThinPexGenerator.java │ │ │ │ └── zipapp │ │ │ │ │ ├── DefaultTemplateProviderOptions.java │ │ │ │ │ ├── ThinZipappGenerator.java │ │ │ │ │ └── ZipappGenerator.java │ │ │ ├── pip │ │ │ │ ├── PipConfFile.java │ │ │ │ ├── PipFreezeAction.java │ │ │ │ └── PipFreezeOutputParser.java │ │ │ └── zipapp │ │ │ │ ├── DefaultPexEntryPointTemplateProvider.java │ │ │ │ ├── DefaultWebappEntryPointTemplateProvider.java │ │ │ │ ├── EntryPointTemplateProvider.java │ │ │ │ └── TemplateProviderOptions.java │ │ │ └── wheel │ │ │ ├── AbiDetails.java │ │ │ ├── EditablePythonAbiContainer.java │ │ │ ├── EmptyWheelCache.java │ │ │ ├── FileBackedWheelCache.java │ │ │ ├── LayeredWheelCache.java │ │ │ ├── PythonAbiContainer.java │ │ │ ├── PythonWheelDetails.java │ │ │ ├── WheelCache.java │ │ │ ├── WheelCacheLayer.java │ │ │ └── internal │ │ │ └── DefaultPythonAbiContainer.java │ └── resources │ │ └── templates │ │ ├── click_tabtab.py │ │ ├── pex_cli_entrypoint.py.template │ │ ├── pex_non_cli_entrypoint.sh.template │ │ ├── setup.py.template │ │ └── wheel-api.py │ └── test │ ├── golang │ ├── README.md │ ├── python26.go │ ├── python27.go │ └── python35.go │ ├── groovy │ └── com │ │ └── linkedin │ │ └── gradle │ │ └── python │ │ ├── PythonExtensionTest.groovy │ │ ├── checkstyle │ │ ├── CheckStyleXmlReporterTest.groovy │ │ ├── StyleViolationTest.groovy │ │ └── model │ │ │ ├── TestFileStyleViolationsContainer.groovy │ │ │ └── TestStyleViolation.groovy │ │ ├── extension │ │ ├── DefaultPythonDetailsTest.groovy │ │ ├── DefaultPythonDetailsUnixTest.groovy │ │ ├── DefaultPythonDetailsWindowsTest.groovy │ │ ├── PythonDefaultVersionsTest.groovy │ │ ├── PythonDetailsTestDouble.java │ │ └── PythonVersionTest.groovy │ │ ├── plugin │ │ ├── PythonCliDistributionPluginTest.groovy │ │ ├── PythonPexDistributionPluginTest.groovy │ │ ├── PythonPluginTest.groovy │ │ └── PythonSourceDistributionPluginTest.groovy │ │ ├── tasks │ │ ├── ParseOutputStreamTest.groovy │ │ ├── action │ │ │ └── pip │ │ │ │ ├── PipActionHelpers.groovy │ │ │ │ ├── PipInstallActionTest.groovy │ │ │ │ ├── PipWheelActionTest.groovy │ │ │ │ └── WheelBuilderTest.groovy │ │ └── exec │ │ │ ├── ExternalExecFailTestDouble.groovy │ │ │ └── ExternalExecTestDouble.groovy │ │ ├── util │ │ ├── DependencyOrderTest.groovy │ │ ├── EnvironmentMergerTest.groovy │ │ ├── PackageInfoTest.groovy │ │ ├── PackageSettingsTest.groovy │ │ ├── WindowsBinaryUnpacker.groovy │ │ ├── internal │ │ │ └── pex │ │ │ │ └── PexExecOutputParserTest.groovy │ │ └── pip │ │ │ └── PipFreezeOutputParserTest.groovy │ │ └── wheel │ │ ├── FileBackedWheelCacheTest.groovy │ │ ├── LayeredWheelCacheTest.groovy │ │ ├── PythonAbiContainerTest.groovy │ │ └── PythonWheelDetailsTest.groovy │ └── resources │ ├── checkstyle │ └── checkstyle.xsd │ └── windows │ └── python │ └── shim │ ├── python26.exe │ ├── python27.exe │ └── python35.exe ├── settings.gradle └── version.properties /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | # Change these settings to your own preference 9 | indent_style = space 10 | indent_size = 4 11 | 12 | # We recommend you to keep these unchanged 13 | end_of_line = lf 14 | charset = utf-8 15 | trim_trailing_whitespace = true 16 | insert_final_newline = true 17 | 18 | # Markdown files sometimes need trailing whitespaces. 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .settings 3 | .classpath 4 | .gradle 5 | .idea 6 | *.iml 7 | *.ipr 8 | *.iws 9 | /build 10 | */build 11 | out/ 12 | */bin/ 13 | *.swp 14 | /ligradle/ 15 | /intTestHomeDir/ 16 | pivy-importer/ivy-repo/ 17 | examples/example-project/activate 18 | examples/example-project/pinned.txt 19 | .DS_Store 20 | -------------------------------------------------------------------------------- /.release-manager.toml: -------------------------------------------------------------------------------- 1 | [github] 2 | owner = "linkedin" 3 | repo = "pygradle" 4 | 5 | [artifactory] 6 | repo="pygradle" 7 | group="com.linkedin.pygradle" 8 | server="https://linkedin.jfrog.io/linkedin" 9 | bintray-repo="maven" 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | jdk: 4 | - openjdk8 5 | 6 | before_cache: 7 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 8 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ 9 | 10 | cache: 11 | directories: 12 | - $HOME/.gradle/caches/ 13 | - $HOME/.gradle/wrapper/ 14 | 15 | script: 16 | - ./gradlew build --console plain 17 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2016 LinkedIn Corp. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | This project uses the following in the plugin: 16 | commons-io (https://commons.apache.org/proper/commons-io/) - Apache License 2.0 17 | Gradle (http://gradle.org) - Apache License 2.0 18 | 19 | This project uses the following part of the python environment: 20 | flake8 (https://gitlab.com/pycqa/flake8) - MIT 21 | pex (https://github.com/pantsbuild/pex) - Apache License 2.0 22 | pip (https://pip.pypa.io/) - MIT 23 | pytest (http://pytest.org) - MIT 24 | pytest-cov (https://github.com/pytest-dev/pytest-cov) - MIT 25 | pytest-xdist (https://github.com/pytest-dev/pytest-xdist) - MIT 26 | setuptools (https://github.com/pypa/setuptools) - MIT 27 | setuptools-git (https://github.com/wichert/setuptools-git) - BSD 28 | six (http://pypi.org/pypi/six/) - MIT 29 | Sphinx (http://sphinx-doc.org/) - BSD 30 | virtualenv (https://virtualenv.pypa.io/) - MIT 31 | wheel (https://github.com/pypa/wheel/) - MIT 32 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | 4 | # Pre-installed Python versions, which Appveyor may upgrade to 5 | # a later point release. 6 | # See: http://www.appveyor.com/docs/installed-software#python 7 | 8 | - PYTHON: "C:\\Python27" 9 | - PYTHON: "C:\\Python33" 10 | - PYTHON: "C:\\Python34" 11 | 12 | build_script: 13 | # Build the compiled extension 14 | - "gradlew.bat assemble" 15 | 16 | test_script: 17 | # Run the project tests 18 | - "gradlew.bat build" 19 | 20 | artifacts: 21 | # Archive the generated packages in the ci.appveyor.com build report. 22 | - path: build\* 23 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'version' 2 | apply plugin: 'idea' 3 | 4 | allprojects { 5 | group = 'com.linkedin.pygradle' 6 | } 7 | 8 | ext { 9 | startTime = System.currentTimeMillis() 10 | } 11 | 12 | subprojects { 13 | apply plugin: 'idea' 14 | apply plugin: 'java' 15 | 16 | buildDir = new File(rootProject.buildDir, path.replace(":", File.separator).substring(0)) 17 | 18 | task downloadDependencies { 19 | description = 'Download all dependencies to help with CI cacheing' 20 | group = 'CI' 21 | doLast { 22 | //noinspection GroovyAssignabilityCheck 23 | configurations.findAll { it.isCanBeResolved() }.each { conf -> 24 | conf.files 25 | } 26 | } 27 | } 28 | 29 | tasks.withType(Test) { task -> 30 | if (gradle.startParameter.consoleOutput == ConsoleOutput.Plain) { //on ci we will see tests as they run 31 | task.afterTest { TestDescriptor td, TestResult tr -> 32 | logger.lifecycle("[${tr.resultType}] - ${td.className}#${td.name}") 33 | 34 | } 35 | task.testLogging.exceptionFormat = 'full' 36 | } 37 | 38 | task.minHeapSize = '128m' 39 | task.maxHeapSize = '512m' 40 | 41 | task.afterSuite { desc, result -> 42 | if (!desc.parent) { // will match the outermost suite 43 | logger.lifecycle("Task {} results: {} ({} tests, {} successes, {} failures, {} skipped)", task.path, 44 | result.resultType, result.testCount, result.successfulTestCount, result.failedTestCount, result.skippedTestCount) 45 | } 46 | } 47 | } 48 | 49 | tasks.matching { it instanceof JavaCompile || it instanceof GroovyCompile }.all { 50 | it.options.compilerArgs += ['-Xlint:deprecation'] 51 | } 52 | 53 | idea { 54 | module { 55 | excludeDirs += buildDir 56 | } 57 | } 58 | 59 | sourceCompatibility = JavaVersion.VERSION_1_8 60 | targetCompatibility = JavaVersion.VERSION_1_8 61 | 62 | /* temporarily disable to fit in CI memory limit 63 | afterEvaluate { 64 | project.tasks.withType(FindBugs) 65 | .matching { it.name != 'findbugsMain' } 66 | .each { it.enabled = false } 67 | } 68 | */ 69 | } 70 | 71 | idea { 72 | project { 73 | jdkName = '1.8' 74 | languageLevel = '1.8' 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /buildSrc/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "java-gradle-plugin" 3 | id 'idea' 4 | } 5 | 6 | repositories { 7 | mavenLocal() 8 | jcenter() 9 | } 10 | 11 | dependencies { 12 | compile gradleApi() 13 | } 14 | 15 | gradlePlugin { 16 | plugins { 17 | versionPlugin { 18 | id = "version" 19 | implementationClass = "com.linkedin.gradle.build.version.VersionPlugin" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /buildSrc/src/main/java/com/linkedin/gradle/build/package-info.java: -------------------------------------------------------------------------------- 1 | package com.linkedin.gradle.build; 2 | -------------------------------------------------------------------------------- /buildSrc/src/main/java/com/linkedin/gradle/build/version/VersionFile.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.build.version; 17 | 18 | import java.io.File; 19 | import java.io.FileReader; 20 | import java.io.IOException; 21 | import java.io.UncheckedIOException; 22 | import java.util.Properties; 23 | 24 | class VersionFile { 25 | private static final String VERSION = "version"; 26 | 27 | static Version getVersion(File propertyFile) { 28 | try { 29 | Properties properties = new Properties(); 30 | properties.load(new FileReader(propertyFile)); 31 | return new Version(properties.getProperty(VERSION)); 32 | } catch (IOException ioe) { 33 | throw new UncheckedIOException(ioe); 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /buildSrc/src/main/java/com/linkedin/gradle/build/version/VersionPlugin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.build.version; 17 | 18 | 19 | import org.gradle.api.Action; 20 | import org.gradle.api.GradleException; 21 | import org.gradle.api.Plugin; 22 | import org.gradle.api.Project; 23 | import org.gradle.api.logging.Logger; 24 | import org.gradle.api.logging.Logging; 25 | 26 | import java.io.File; 27 | 28 | public class VersionPlugin implements Plugin { 29 | 30 | Logger logger = Logging.getLogger(VersionPlugin.class); 31 | 32 | @Override 33 | public void apply(Project target) { 34 | if (target.getRootProject() != target) { 35 | throw new GradleException("Cannot apply dependency plugin to a non-root project"); 36 | } 37 | 38 | File versionProperties = target.file("version.properties"); 39 | 40 | Version version = VersionFile.getVersion(versionProperties); 41 | 42 | if (!target.hasProperty("release") || !Boolean.parseBoolean((String) target.property("release"))) { 43 | version = version.asSnapshot(); 44 | } 45 | 46 | logger.lifecycle("Building using version {}", version); 47 | target.allprojects(new VersionAction(version)); 48 | } 49 | 50 | private static class VersionAction implements Action { 51 | private final Version version; 52 | 53 | public VersionAction(Version version) { 54 | this.version = version; 55 | } 56 | 57 | @Override 58 | public void execute(Project project) { 59 | project.setVersion(version); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /codequality/HEADER: -------------------------------------------------------------------------------- 1 | Copyright 2016 LinkedIn Corp. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | -------------------------------------------------------------------------------- /codequality/checkstyle/suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /codequality/findbugs/findbugsExclude.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /docs/changelog.md: -------------------------------------------------------------------------------- 1 | # pygradle news # 2 | 3 | * 2019-04-08 4 | - You must now change any usages of either the `python.pex.FatPex` or 5 | `python.pex.isFat` flag to `python.zipapp.isFat`. 6 | 7 | * 2018-11-09 8 | - Added a default `mypy` task which you can enable by setting 9 | `python.mypy.run = true` in your `build.gradle` file. 10 | 11 | * Earlier 12 | - Lots! 13 | -------------------------------------------------------------------------------- /docs/compatibility.md: -------------------------------------------------------------------------------- 1 | # Compatibility 2 | 3 | ## Linux 4 | 5 | There aren't any known compatibility issues on Linux. 6 | 7 | ## OS X 8 | 9 | There aren't any known compatibility issues on OS X. 10 | 11 | ## Windows 12 | 13 | PyGradle has basic support for Windows. We would like to note that pex is not 14 | offically supported on Windows, while it seems to work properly we cannot 15 | guarantee it. 16 | 17 | We're eager to review pull requests from our Windows user base! 18 | 19 | ## Python 20 | 21 | pygradle currently supports Python 2.7, and 3.5 - 3.7. 22 | 23 | ### Caveats 24 | 25 | - The integration tests require that Python be available in your PATH. 26 | - The pex format defaults to "fat" - meaning each pex contains all dependencies.. 27 | - All pex files are renamed to include a `.py` extension, whereas on Unix based systems they are `.pex`. 28 | - You must have Python installed and the `.py` extension needs to be registered to the Python interpreter you want to run. 29 | -------------------------------------------------------------------------------- /docs/etc.md: -------------------------------------------------------------------------------- 1 | The Python ecosystem is extremely productive in aspects such as local 2 | development, artifact management, library functionality, and more. But, the 3 | Python ecosystem does struggle with things like dependency management, 4 | dependency resolution, conflict resolution, integration with existing metadata 5 | systems, and more. Projects that are built with the PyGradle build system get 6 | the best of both worlds by leveraging each tool, Python and Gradle, only for 7 | what it is good at. 8 | 9 | The @linkedin/pygradle-devs team feels the major advantages of using PyGradle, 10 | among many others, are the following. 11 | 12 | - You get real dependency resolution as opposed to 'mock' dependency resolution. 13 | - You get conflict resolution which resolves the notorious VersionConflict and DistributionNotFound errors that plague large projects. 14 | - You get the Gradle cache which is an enterprise quality caching solution that many organizations leverage. 15 | - You get the ability to build your Python code alongside your Java, Scala, and C++ code in a first class way. 16 | - You get a pluggable build system that can quickly be customized or adapted to support new technologies like pex. 17 | - You get to integrate completely with the metadata systems that teams have spent millions of engineering dollars working on. 18 | 19 | -------------------------------------------------------------------------------- /docs/plugins/python-cli.md: -------------------------------------------------------------------------------- 1 | # com.linkedin.python-cli 2 | 3 | ## About 4 | 5 | The `com.linkedin.python-cli` extends the `com.linkedin.python-pex` plugin so that TAB completion scripts can be generated when projects use Click. 6 | 7 | ## Usage 8 | 9 | ``` 10 | plugins { 11 | id 'com.linkedin.python-cli', version 12 | } 13 | ``` 14 | 15 | TAB completion generation is disabled by default, to enable it do the following: 16 | 17 | ``` 18 | python.cli.generateCompletions = true 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/plugins/python-flyer.md: -------------------------------------------------------------------------------- 1 | # com.linkedin.python-flyer 2 | 3 | ## About 4 | 5 | // TODO 6 | 7 | ## Usage 8 | 9 | ``` 10 | plugins { 11 | id 'com.linkedin.python-flyer', version 12 | } 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/plugins/python-pex.md: -------------------------------------------------------------------------------- 1 | # com.linkedin.python-pex 2 | 3 | ## About 4 | 5 | The `com.linkedin.python-pex` plugin creates a [pex](https://pex.readthedocs.io/en/stable/) file as an artifact of the build. This pex comes in two flavors. The first is a 'fat pex' meaning that there will be a pex built for every console script in the project. In contrast, there are 'thin pex' files, where there is a single pex file created, but a script per application will be generated pointing at the pex file. By default, pygradle will generate thin pex applications. The `com.linkedin.python-pex` updates the `python` closure to include a `pex` option. 6 | 7 | Here are the options provided by the added `pex` option: 8 | ``` 9 | python { 10 | pex { 11 | fatPex = false // default 12 | pexCache = project.file("$buildDir/pex-cache") // default 13 | } 14 | } 15 | ``` 16 | 17 | The `com.linkedin.python-pex` plugin also applies [`com.linkedin.python`](./python.md). 18 | 19 | ## Usage 20 | 21 | ### Standard Usage 22 | 23 | ``` 24 | plugins { 25 | id 'com.linkedin.python-pex', version 26 | } 27 | ``` 28 | 29 | ### Fat Pex Usage 30 | ``` 31 | plugins { 32 | id 'com.linkedin.python-pex', version 33 | } 34 | 35 | python.pex.fatPex = true 36 | ``` 37 | -------------------------------------------------------------------------------- /docs/plugins/python-sdist.md: -------------------------------------------------------------------------------- 1 | # com.linkedin.python-sdist 2 | 3 | 4 | ## About 5 | The `com.linkedin.python-sdist` plugin is a plugin that builds a source distribution, called an sdist, for your Python project. It is typically used for library type projects but can also be used to package data and static content. 6 | 7 | ## Usage 8 | 9 | ``` 10 | plugins { 11 | id 'com.linkedin.python-sdist', version 12 | } 13 | ``` 14 | 15 | ## Artifacts 16 | 17 | The `com.linkedin.python-sdist` plugin should create a single artifact from the build. 18 | The artifact will be named -.tar.gz and will be created in the subproject's directory in the dist directory. 19 | 20 | ``` 21 | 22 | ├── build.gradle 23 | ├── setup.cfg 24 | ├── setup.py 25 | ├── ... 26 | ├── build 27 | │ └── dist 28 | │ └── sample-lib-0.2.19.tar.gz 29 | └── ... 30 | ``` 31 | -------------------------------------------------------------------------------- /docs/plugins/python-web-app.md: -------------------------------------------------------------------------------- 1 | # com.linkedin.python-web-app 2 | 3 | ## About 4 | 5 | // TODO 6 | 7 | ## Usage 8 | 9 | ``` 10 | plugins { 11 | id 'com.linkedin.python-web-app', version 12 | } 13 | ``` 14 | -------------------------------------------------------------------------------- /examples/example-project/.gitignore: -------------------------------------------------------------------------------- 1 | *.egg 2 | *.egg-info/ 3 | *.pyc 4 | *.pyo 5 | *.sublime-* 6 | .coverage 7 | .env 8 | .gradle/ 9 | .idea/ 10 | .tox* 11 | .venv* 12 | /*/*pinned.txt 13 | /*/MANIFEST 14 | /*/activate 15 | /*/build/ 16 | /*/coverage.xml 17 | /*/dist/ 18 | /*/htmlcov/ 19 | /build/ 20 | /config/ 21 | /dist/ 22 | /gradle 23 | /ligradle/ 24 | TEST-*.xml 25 | -------------------------------------------------------------------------------- /examples/example-project/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | ivy { 5 | url "${System.getProperty("user.home")}/local-repo" 6 | layout('pattern') { 7 | ivy "[organisation]/[module]/[revision]/[module]-[revision].ivy" 8 | artifact "[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier]).[ext]" 9 | m2compatible true 10 | } 11 | } 12 | } 13 | dependencies{ 14 | classpath 'com.linkedin.pygradle:pygradle-plugin:0.7.1-SNAPSHOT' 15 | } 16 | } 17 | 18 | apply plugin: "com.linkedin.python-sdist" 19 | apply plugin: com.linkedin.gradle.python.plugin.WheelFirstPlugin 20 | 21 | dependencies { 22 | python 'pypi:requests:2.9.1' 23 | test 'pypi:mock:1.3.0' 24 | } 25 | 26 | repositories { 27 | pyGradlePyPi() 28 | } 29 | -------------------------------------------------------------------------------- /examples/example-project/settings.gradle: -------------------------------------------------------------------------------- 1 | // This file is empty to allow you to test the example project, despite being a subdirectory of a 2 | // larger gradle repository. 3 | -------------------------------------------------------------------------------- /examples/example-project/setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = E121,E123,E226,W292 3 | max-line-length = 160 4 | -------------------------------------------------------------------------------- /examples/example-project/src/helloworld/__init__.py: -------------------------------------------------------------------------------- 1 | # This is a package. 2 | -------------------------------------------------------------------------------- /examples/example-project/test/test_example.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linkedin/pygradle/3e724f26d9dece4746623a7e94028fef438e7cbc/examples/example-project/test/test_example.py -------------------------------------------------------------------------------- /examples/iris-classification/.gitignore: -------------------------------------------------------------------------------- 1 | *.egg 2 | *.egg-info/ 3 | *.pyc 4 | *.pyo 5 | *.sublime-* 6 | .coverage 7 | .env 8 | .gradle/ 9 | .idea/ 10 | .tox* 11 | .venv* 12 | /*/*pinned.txt 13 | /*/MANIFEST 14 | /*/activate 15 | /*/build/ 16 | /*/coverage.xml 17 | /*/dist/ 18 | /*/htmlcov/ 19 | /build/ 20 | /config/ 21 | /dist/ 22 | /gradle 23 | /ligradle/ 24 | TEST-*.xml 25 | /pinned.txt 26 | -------------------------------------------------------------------------------- /examples/iris-classification/README.md: -------------------------------------------------------------------------------- 1 | # Example Machine Learning Project (SVM on the IRIS dataset) 2 | 3 | This example project will give you a starting point for applying and/or developing machine learning algorithms for the analysis of your data. 4 | For the sake of simplicity, we will use the classical text-book example using an SVM from the scipy/numpy/sklearn stack on the IRIS dataset. 5 | 6 | If not already done, please have a look at the example-project for the general pygradle project setup; this example assumes a working environment on your local computer. 7 | 8 | Note that all the following commands assume that your PWD is this example project. 9 | 10 | ## One-time project setup 11 | 12 | As for the example project, you will need a setup.py in order to build the project; therefore, generate it (once) by calling: 13 | 14 | ``` 15 | $ ./gradlew generateSetupPy 16 | ``` 17 | 18 | Note that the setup.py file already exists and has been customized in order to generate an executable (see below). 19 | This required to define an entry_point for console_scripts, which now points to the main function which is responsible to train the classifier. 20 | 21 | ## Dependencies 22 | 23 | We will be using the pivy-importer to import all dependencies of our local project from pypi into a local file system path. 24 | Here, it is assumed that the pivy-importer is available on your local machine (it merely boils down to a ./gradlew assemble in that directory, and maybe copying the assembled jar to an appropriate place). 25 | For convenience, I am using the shadowJar of the pivy-importer, though the other should work as well. 26 | 27 | For the dependencies of this sample project, you can import those using 28 | 29 | java -jar ../../build/pivy-importer/libs/pivy-importer-0.3.36-SNAPSHOT-all.jar --repo /tmp/repo numpy:1.11.2 pandas:0.19.1 scipy:0.18.1 scikit-learn:0.18 python-dateutil:2.6.0 --replace numpy:1.6.1:numpy=1.11.2,scipy:0.9.0=scipy:0.18.1 30 | 31 | and as a pre-requisite, make sure that development tools (python developement headers, etc.) are present such that the libraries above can be compiled. 32 | 33 | ## Building the project 34 | 35 | You are now ready to run your build, using 36 | 37 | ``` 38 | $ ./gradlew build 39 | ``` 40 | 41 | Then, after a longish build run, you should be able to call the executable in order to see the output which should be similar to this: 42 | 43 | # ./build/deployable/bin/classify_iris 44 | Accuracy score on the IRIS dataset using a 60/40 split: 0.966666666667 45 | 46 | Great! You just managed to use the scipy/numpy/sklearn stack with pygradle! 47 | -------------------------------------------------------------------------------- /examples/iris-classification/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.linkedin.python-cli" version "0.4.9" 3 | } 4 | 5 | version=1.0 6 | 7 | // we need to define an explicit installation sequence for the dependencies, 8 | // see issue 75 here: https://github.com/linkedin/pygradle/issues/75 9 | project.tasks.findByName('installPythonRequirements').sorted = false 10 | 11 | dependencies { 12 | python 'pypi:numpy:1.11.2' 13 | python 'pypi:pandas:0.19.1' 14 | python 'pypi:scipy:0.18.1' 15 | python 'pypi:scikit-learn:0.18' 16 | // pandas depends on pytz>=2011k, which cannot be satisfied automatically 17 | // since 2011k does not conform to correct versioning (see 18 | // http://stackoverflow.com/questions/18230956/could-not-find-a-version-that-satisfies-the-requirement-pytz) 19 | // therefore, explicitly include dependencies here: 20 | python 'pypi:pytz:2016.4' 21 | python 'pypi:python-dateutil:2.6.0' 22 | } 23 | 24 | repositories { 25 | pyGradlePyPi() 26 | 27 | // as LinkedIn only provides an initial set of pypi libraries along with Ivy-Metadata, 28 | // we will use a local repository which is populated with libraries and metadata using 29 | // the pivy-importer. 30 | ivy{ 31 | url "/tmp/repo" 32 | layout 'pattern' , { 33 | artifact '[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier]).[ext]' 34 | ivy '[organisation]/[module]/[revision]/[module]-[revision].ivy' 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/iris-classification/settings.gradle: -------------------------------------------------------------------------------- 1 | // This file is empty to allow you to test the example project, despite being a subdirectory of a 2 | // larger gradle repository. 3 | -------------------------------------------------------------------------------- /examples/iris-classification/src/classify_iris/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = ["run"] 2 | -------------------------------------------------------------------------------- /examples/iris-classification/src/classify_iris/run.py: -------------------------------------------------------------------------------- 1 | from sklearn.model_selection import train_test_split 2 | from sklearn import datasets 3 | from sklearn import svm 4 | 5 | 6 | def main(): 7 | iris = datasets.load_iris() 8 | 9 | X_train, X_test, y_train, y_test = train_test_split( 10 | iris.data, iris.target, test_size=0.4, random_state=0) 11 | 12 | clf = svm.SVC(kernel='linear', C=1).fit(X_train, y_train) 13 | print("Accuracy score on the IRIS dataset using a 60/40 split: {}" 14 | .format(clf.score(X_test, y_test))) 15 | 16 | if __name__ == '__main__': 17 | print("This is not printed when run with PEX ...") 18 | main() 19 | -------------------------------------------------------------------------------- /examples/iris-classification/test/test_dataset.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from sklearn import datasets 3 | 4 | 5 | class Test(unittest.TestCase): 6 | 7 | def testIrisDataset(self): 8 | iris = datasets.load_iris() 9 | self.assertEqual(150, 10 | len(iris.data), 11 | "The dataset shall contain 150 instances") 12 | 13 | 14 | if __name__ == "__main__": 15 | # import sys;sys.argv = ['', 'Test.testIrisDataset'] 16 | unittest.main() 17 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | #long-running Gradle process speeds up local builds 2 | #to stop the daemon run 'ligradle --stop' 3 | org.gradle.daemon=true 4 | 5 | #configures only relevant projects to speed up the configuration of large projects 6 | #useful when specific project/task is invoked e.g: ligradle :cloud:cloud-api:build 7 | org.gradle.configureondemand=true 8 | 9 | #Gradle will run tasks from subprojects in parallel 10 | #Higher CPU usage, faster builds 11 | org.gradle.parallel=true 12 | org.gradle.jvmargs=-Xmx1g -Xms256m 13 | -------------------------------------------------------------------------------- /gradle/code-quality.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'codenarc' 2 | // apply plugin: 'findbugs' 3 | apply plugin: 'checkstyle' 4 | 5 | checkstyle { 6 | configFile = file("$rootDir/codequality/checkstyle/checkstyle.xml") 7 | toolVersion = '6.19' 8 | } 9 | 10 | tasks.withType(Checkstyle) { 11 | reports { 12 | xml.enabled false 13 | html.enabled true 14 | } 15 | ignoreFailures = false 16 | } 17 | 18 | codenarc { 19 | configFile = file("$rootDir/codequality/codenarc/rules.groovy") 20 | } 21 | 22 | /* Temporarily disable to stay within CI memory limit 23 | findbugs { 24 | ignoreFailures = true 25 | } 26 | 27 | tasks.withType(FindBugs) { 28 | reports { 29 | xml.enabled = false 30 | html.enabled = true 31 | } 32 | } 33 | */ 34 | 35 | tasks.build.dependsOn(tasks.checkstyleMain) 36 | -------------------------------------------------------------------------------- /gradle/publishing.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'maven-publish' 2 | apply plugin: 'ivy-publish' 3 | 4 | task sourceJar(type: Jar) { 5 | classifier = 'sources' 6 | from sourceSets.main.allSource 7 | } 8 | 9 | task javadocJar(type: Jar, dependsOn: groovydoc) { 10 | classifier = 'javadoc' 11 | from groovydoc.destinationDir 12 | } 13 | 14 | artifacts { 15 | archives sourceJar 16 | archives javadocJar 17 | } 18 | 19 | publishing { 20 | publications { 21 | standardjava(MavenPublication) { 22 | from components.java 23 | artifact sourceJar 24 | artifact javadocJar 25 | pom.withXml { 26 | asNode().children().last() + { 27 | resolveStrategy = Closure.DELEGATE_FIRST 28 | description 'PyGradle, Building Python with Gradle' 29 | url 'https://github.com/linkedin/pygradle' 30 | scm { 31 | url 'https://github.com/linkedin/pygradle' 32 | connection 'scm:git:git://github.com/linkedin/pygradle.git' 33 | developerConnection 'scm:git:ssh:git@github.com:linkedin/pygradle.git' 34 | } 35 | licenses { 36 | license { 37 | name 'The Apache Software License, Version 2.0' 38 | url 'http://www.apache.org/license/LICENSE-2.0.txt' 39 | distribution 'repo' 40 | } 41 | } 42 | } 43 | } 44 | } 45 | ivyJava(IvyPublication) { 46 | if (project.version.isSnapshot() && project.hasProperty('enableLongVersion')) { 47 | revision = project.version.withBuildNumber(startTime) 48 | println("Testing Version : " + revision) 49 | } 50 | from components.java 51 | } 52 | } 53 | repositories { 54 | ivy { 55 | url "${System.getProperty("user.home")}/local-repo" 56 | layout('pattern') { 57 | ivy "[organisation]/[module]/[revision]/[module]-[revision].ivy" 58 | artifact "[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier]).[ext]" 59 | m2compatible true 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linkedin/pygradle/3e724f26d9dece4746623a7e94028fef438e7cbc/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-bin.zip 6 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /pivy-importer/src/main/groovy/com/linkedin/python/importer/deps/DependencySubstitution.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.python.importer.deps 17 | 18 | class DependencySubstitution { 19 | 20 | final Map replacementMap 21 | final Map forceMap 22 | 23 | DependencySubstitution(Map replacementMap, Map forceMap) { 24 | this.replacementMap = replacementMap 25 | this.forceMap = forceMap 26 | } 27 | 28 | String maybeReplace(String dependency) { 29 | def name = dependency.split(":")[0] 30 | if (forceMap.containsKey(name)) { 31 | return "$name:${forceMap[name]}" 32 | } 33 | if (replacementMap.containsKey(dependency)) { 34 | return replacementMap.get(dependency) 35 | } else { 36 | return dependency 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /pivy-importer/src/main/groovy/com/linkedin/python/importer/pypi/PypiApiCache.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.python.importer.pypi 17 | 18 | import com.linkedin.python.importer.util.ProxyDetector 19 | import groovy.json.JsonSlurper 20 | import groovy.util.logging.Slf4j 21 | import org.apache.http.client.HttpResponseException 22 | import org.apache.http.client.fluent.Request 23 | 24 | 25 | @Slf4j 26 | class PypiApiCache { 27 | 28 | Map cache = [:].withDefault { String name -> new ProjectDetails(downloadMetadata(name)) } 29 | 30 | ProjectDetails getDetails(String project, boolean lenient) { 31 | try { 32 | return cache.get(project) 33 | } catch(HttpResponseException httpResponseException) { 34 | String msg = "Package ${project} has an illegal module name, " + 35 | "we are not able to find it on PyPI (https://pypi.org/pypi/$project/json)" 36 | if (lenient) { 37 | log.error("$msg. ${httpResponseException.message}") 38 | return null 39 | } 40 | throw new IllegalArgumentException("$msg. ${httpResponseException.message}") 41 | } 42 | } 43 | 44 | static private Map downloadMetadata(String dependency) { 45 | def url = "https://pypi.org/pypi/$dependency/json" 46 | log.debug("Metadata url: {}", url) 47 | def proxy = ProxyDetector.maybeGetHttpProxy() 48 | 49 | def builder = Request.Get(url) 50 | if (null != proxy) { 51 | builder = builder.viaProxy(proxy) 52 | } 53 | def content = builder.connectTimeout(10000) 54 | .socketTimeout(10000) 55 | .execute().returnContent().asString() 56 | 57 | def object = new JsonSlurper().parseText(content) 58 | return (Map) object 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /pivy-importer/src/main/groovy/com/linkedin/python/importer/pypi/VersionEntry.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.python.importer.pypi 17 | 18 | import groovy.transform.TupleConstructor 19 | 20 | @TupleConstructor 21 | class VersionEntry { 22 | 23 | String url 24 | String packageType 25 | String filename 26 | } 27 | -------------------------------------------------------------------------------- /pivy-importer/src/main/groovy/com/linkedin/python/importer/util/ProxyDetector.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.python.importer.util 17 | 18 | import groovy.util.logging.Slf4j 19 | import org.apache.http.HttpHost 20 | 21 | @Slf4j 22 | class ProxyDetector { 23 | 24 | private final static String HTTP_PROXY_HOST = "http.proxyHost" 25 | private final static String HTTP_PROXY_PORT = "http.proxyPort" 26 | 27 | static maybeGetHttpProxy() { 28 | int proxyPort = -1 29 | 30 | def proxyPortString = System.getProperty(HTTP_PROXY_PORT) 31 | def proxyHost = System.getProperty(HTTP_PROXY_HOST) 32 | 33 | def proxy = null 34 | if (null != proxyPortString && null != proxyHost) { 35 | log.debug("Detected {} = '{}' and {} = '{}' in the system properties.", HTTP_PROXY_HOST, proxyHost, HTTP_PROXY_PORT, proxyPortString) 36 | try { 37 | proxyPort = Integer.valueOf(proxyPortString) 38 | proxy = new HttpHost(proxyHost, proxyPort) 39 | } catch (NumberFormatException e) { 40 | // number could not be parsed as a number. 41 | log.warn("Unable to parse {} as a number. No proxy configured!", proxyPortString) 42 | } 43 | } 44 | return proxy 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /pivy-importer/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | 21 | 23 | 24 | %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /pivy-importer/src/test/groovy/com/linkedin/python/importer/deps/DependencySubstitutionTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.python.importer.deps 17 | 18 | import spock.lang.Specification 19 | 20 | class DependencySubstitutionTest extends Specification { 21 | def "replace dependency from replacementMap and forceMap"() { 22 | Map replacementMap = [ 23 | "module1:originalVersion1": "module1:replacedVersion1", 24 | "module2:originalVersion2": "module2:replacedVersion2", 25 | "module3:originalVersion3": "module3:replacedVersion3"] 26 | 27 | Map forceMap = ["module3": "forceVersion3", "module4": "forceVersion4"] 28 | DependencySubstitution testDependencySubstitution = new DependencySubstitution(replacementMap, forceMap) 29 | 30 | when: 31 | String dependency2 = "module2:originalVersion2" 32 | String result = testDependencySubstitution.maybeReplace(dependency2) 33 | then: 34 | assert "module2:replacedVersion2" == result 35 | 36 | when: 37 | String dependency3 = "module3:originalVersion3" 38 | result = testDependencySubstitution.maybeReplace(dependency3) 39 | then: 40 | assert "module3:forceVersion3" == result 41 | 42 | when: 43 | String dependency4 = "module4:originalVersion5" 44 | result = testDependencySubstitution.maybeReplace(dependency4) 45 | then: 46 | assert "module4:forceVersion4" == result 47 | 48 | when: 49 | String dependency5 = "module5:originalVersion5" 50 | result = testDependencySubstitution.maybeReplace(dependency5) 51 | then: 52 | assert "module5:originalVersion5" == result 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /pivy-importer/src/test/groovy/com/linkedin/python/importer/distribution/PythonPackageTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.python.importer.distribution 17 | 18 | import com.linkedin.python.importer.deps.DependencySubstitution 19 | import com.linkedin.python.importer.pypi.PypiApiCache 20 | import groovy.transform.InheritConstructors 21 | import spock.lang.Specification 22 | 23 | @InheritConstructors 24 | class TestPythonPackage extends PythonPackage { 25 | @Override 26 | Map> getDependencies(boolean latestVersions, 27 | boolean allowPreReleases, 28 | boolean fetchExtras, 29 | boolean lenient) { 30 | return [:] 31 | } 32 | } 33 | 34 | class PythonPackageTest extends Specification { 35 | private File testDirectory 36 | private TestPythonPackage testPythonPackage 37 | 38 | def setup() { 39 | testDirectory = new File(getClass().getClassLoader().getResource("deps").getFile()) 40 | File testPackageFile = new File(testDirectory, "WMI-1.4.8.zip") 41 | DependencySubstitution testDependencySubstitution = new DependencySubstitution([:], [:]) 42 | PypiApiCache testPypiApiCache = new PypiApiCache() 43 | 44 | testPythonPackage = new TestPythonPackage("WMI", "1.4.8", testPackageFile, 45 | testPypiApiCache, testDependencySubstitution) 46 | } 47 | 48 | def "test explode zip for target entry"() { 49 | given: 50 | String testFileName = "PKG-INFO" 51 | String testEntryName = "WMI-1.4.8/$testFileName" 52 | when: 53 | String actualText = testPythonPackage.explodeZipForTargetEntry(testEntryName).replaceAll("\r\n", "\n") 54 | String expectedText = new File(testDirectory, "$testFileName").text 55 | then: 56 | actualText == expectedText 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /pivy-importer/src/test/groovy/com/linkedin/python/importer/distribution/SourceDistPacakgeTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.python.importer.distribution 17 | 18 | import com.linkedin.python.importer.deps.DependencySubstitution 19 | import com.linkedin.python.importer.pypi.PypiApiCache 20 | import spock.lang.Specification 21 | 22 | class SourceDistPacakgeTest extends Specification { 23 | private File testDirectory 24 | private SourceDistPackage testSourceDistPackage 25 | 26 | def setup() { 27 | testDirectory = new File(getClass().getClassLoader().getResource("deps").getFile()) 28 | File testPackageFile = new File(testDirectory, "python-dateutil-2.7.3.tar.gz") 29 | DependencySubstitution testDependencySubstitution = new DependencySubstitution([:], [:]) 30 | PypiApiCache testPypiApiCache = new PypiApiCache() 31 | 32 | testSourceDistPackage = new SourceDistPackage("python-dateutil", "2.7.3", testPackageFile, 33 | testPypiApiCache, testDependencySubstitution) 34 | } 35 | 36 | def "test parse requires text"() { 37 | when: 38 | String actualResult = testSourceDistPackage.getRequiresTextFile() 39 | String expectedResult = "six>=1.5\n" 40 | then: 41 | actualResult == expectedResult 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /pivy-importer/src/test/groovy/com/linkedin/python/importer/ivy/IvyFileWriterTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.python.importer.ivy 17 | 18 | import com.linkedin.python.importer.pypi.VersionEntry 19 | import org.junit.Rule 20 | import org.junit.rules.TemporaryFolder 21 | import spock.lang.Specification 22 | 23 | class IvyFileWriterTest extends Specification { 24 | @Rule TemporaryFolder tempDir = new TemporaryFolder() 25 | private File testDirectory 26 | File testTempFolder 27 | 28 | def setup() { 29 | testTempFolder = tempDir.newFolder("testTemp") 30 | testDirectory = new File(getClass().getClassLoader().getResource("deps").getFile()) 31 | } 32 | 33 | def "test writing Ivy file for wheel which has runtime dependencies"() { 34 | given: 35 | VersionEntry testVersionEntry = new VersionEntry("testURI", "wheel", "Django-2.0.6-py3-none-any.whl") 36 | IvyFileWriter testIvyFileWriter = new IvyFileWriter("Django", "2.0.6", "bdist_wheel", [testVersionEntry]) 37 | Map> testDepsMap = [:] 38 | testDepsMap["argon2"] = ["argon2_cffi:18.1.0"] 39 | testDepsMap["bcrypt"] = ["bcrypt:3.1.4"] 40 | testDepsMap["default"] = ["pytz:2018.5"] 41 | 42 | when: 43 | testIvyFileWriter.writeIvyFile(testTempFolder, testDepsMap, "py3-none-any") 44 | String actualIvyContent = new File(testTempFolder, "Django-2.0.6-py3-none-any.ivy").text.trim() 45 | String expectedIvyContent = new File(testDirectory, "Django-2.0.6-py3-none-any.ivy").text.trim() 46 | 47 | then: 48 | actualIvyContent == expectedIvyContent 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /pivy-importer/src/test/resources/deps/Django-2.0.6-py3-none-any.ivy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /pivy-importer/src/test/resources/deps/Django-2.0.6-py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linkedin/pygradle/3e724f26d9dece4746623a7e94028fef438e7cbc/pivy-importer/src/test/resources/deps/Django-2.0.6-py3-none-any.whl -------------------------------------------------------------------------------- /pivy-importer/src/test/resources/deps/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 1.0 2 | Name: WMI 3 | Version: 1.4.8 4 | Summary: Windows Management Instrumentation 5 | Home-page: http://timgolden.me.uk/python/wmi.html 6 | Author: Tim Golden 7 | Author-email: mail@timgolden.me.uk 8 | License: http://www.opensource.org/licenses/mit-license.php 9 | Description: UNKNOWN 10 | Platform: UNKNOWN 11 | -------------------------------------------------------------------------------- /pivy-importer/src/test/resources/deps/WMI-1.4.8.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linkedin/pygradle/3e724f26d9dece4746623a7e94028fef438e7cbc/pivy-importer/src/test/resources/deps/WMI-1.4.8.zip -------------------------------------------------------------------------------- /pivy-importer/src/test/resources/deps/python-dateutil-2.7.3.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linkedin/pygradle/3e724f26d9dece4746623a7e94028fef438e7cbc/pivy-importer/src/test/resources/deps/python-dateutil-2.7.3.tar.gz -------------------------------------------------------------------------------- /pivy-importer/src/test/resources/deps/pywin32-223-cp27-cp27m-win_amd64.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linkedin/pygradle/3e724f26d9dece4746623a7e94028fef438e7cbc/pivy-importer/src/test/resources/deps/pywin32-223-cp27-cp27m-win_amd64.whl -------------------------------------------------------------------------------- /pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/CleanLeaveVenvTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.plugin 17 | 18 | import com.linkedin.gradle.python.plugin.testutils.DefaultBlankProjectLayoutRule 19 | import com.linkedin.gradle.python.plugin.testutils.PyGradleTestBuilder 20 | import com.linkedin.gradle.python.util.StandardTextValues 21 | import org.gradle.testkit.runner.GradleRunner 22 | import org.junit.Rule 23 | import spock.lang.Specification 24 | 25 | /** 26 | * This test class is designed to test scenarios where we are only using pygradle for documentation and 27 | * other scenarios where it may be included in the project, but not active. 28 | */ 29 | class CleanLeaveVenvTest extends Specification { 30 | 31 | @Rule 32 | final DefaultBlankProjectLayoutRule testProjectDir = new DefaultBlankProjectLayoutRule() 33 | 34 | def "Cleans everything from build except venv folder"() { 35 | 36 | given: 37 | testProjectDir.buildFile << """ 38 | | plugins { 39 | | id 'com.linkedin.python' 40 | | } 41 | | ${PyGradleTestBuilder.createRepoClosure()} 42 | """.stripMargin().stripIndent() 43 | 44 | def fvenv = testProjectDir.newFolder(testProjectDir.PROJECT_NAME_DIR, "build", "venv") 45 | def ftest2 = testProjectDir.newFolder(testProjectDir.PROJECT_NAME_DIR, "build", "test2") 46 | def ftest1 = testProjectDir.newFile("${testProjectDir.PROJECT_NAME_DIR + "/build"}/test1.txt") 47 | 48 | when: 49 | def result = GradleRunner.create() 50 | .withProjectDir(testProjectDir.root) 51 | .withArguments(StandardTextValues.TASK_CLEAN_SAVE_VENV.value) 52 | .withPluginClasspath() 53 | .withDebug(true) 54 | .build() 55 | println result.output 56 | 57 | then: "make sure it passes initially first" 58 | !ftest1.exists() 59 | !ftest2.exists() 60 | fvenv.exists() 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/testutils/ExecUtils.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.plugin.testutils 17 | 18 | import com.linkedin.gradle.python.util.OperatingSystem 19 | import org.apache.commons.exec.CommandLine 20 | import org.apache.commons.exec.DefaultExecutor 21 | import org.apache.commons.exec.ExecuteException 22 | import org.apache.commons.exec.PumpStreamHandler 23 | 24 | import java.nio.file.Path 25 | 26 | class ExecUtils { 27 | 28 | static String run(Path path) { 29 | return run(path, new String[0]) 30 | } 31 | 32 | static String run(Path path, String... args) { 33 | 34 | ByteArrayOutputStream os = new ByteArrayOutputStream() 35 | def executor = new DefaultExecutor() 36 | executor.streamHandler = new PumpStreamHandler(os) 37 | 38 | def cmd = OperatingSystem.current().isWindows() ? "python ${path.toString()}" : path.toString() 39 | 40 | def commandLine = CommandLine.parse(cmd) 41 | commandLine.addArguments(args) 42 | 43 | try { 44 | executor.execute(commandLine) 45 | } catch (ExecuteException exception) { 46 | println(os.toString()) 47 | throw exception 48 | } 49 | 50 | return os.toString() 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/testutils/ProjectLayoutRule.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.plugin.testutils 17 | 18 | trait ProjectLayoutRule { 19 | abstract void before() throws Throwable 20 | 21 | abstract void after() 22 | 23 | abstract void create() throws IOException 24 | 25 | abstract File newFile(String fileName) throws IOException 26 | 27 | abstract File newFile() throws IOException 28 | 29 | abstract File newFolder(String folder) throws IOException 30 | 31 | abstract File newFolder(String... folderNames) throws IOException 32 | 33 | abstract File newFolder() throws IOException 34 | 35 | abstract File getRoot() 36 | 37 | abstract void delete() 38 | } 39 | -------------------------------------------------------------------------------- /pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/testutils/PyGradleTestBuilder.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.plugin.testutils 17 | 18 | class PyGradleTestBuilder { 19 | 20 | static String createSetupCfg() { 21 | return '''\ 22 | | [flake8] 23 | | ignore = E121,E123,E226,W292 24 | | max-line-length = 160 25 | | 26 | | [tool:pytest] 27 | | addopts = --ignore build/ --ignore dist/ 28 | '''.stripMargin().stripIndent() 29 | } 30 | 31 | static String createSetupPy() { 32 | return PyGradleTestBuilder.getResource("/templates/setup.py.template").text + '\n' + PyGradleTestBuilder.getResource("/setup.py").text 33 | } 34 | 35 | static String createSettingGradle() { 36 | return """\ 37 | | rootProject.name = 'testProject' 38 | |""".stripMargin().stripIndent() 39 | } 40 | 41 | /** 42 | * A convenience method that uses the uri path that is set in the build.gradle file for the ivy location 43 | * 44 | * @return 45 | */ 46 | static String createRepoClosure() { 47 | createRepoClosure(System.getenv('TEST_REPO')) 48 | } 49 | 50 | /** 51 | * Creates a local repo block for a test 52 | * @param pathURL path url to use as the location of the ivy project 53 | * @return 54 | */ 55 | static String createRepoClosure(String pathURL) { 56 | return """\ 57 | |repositories { 58 | | ivy { 59 | | name 'pypi-local' 60 | url "${pathURL}" 61 | | layout "pattern", { 62 | | ivy "[organisation]/[module]/[revision]/[module]-[revision].ivy" 63 | | artifact "[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier]).[ext]" 64 | | m2compatible = true 65 | | } 66 | | } 67 | |}""".stripMargin().stripIndent() 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/plugin/testutils/PythonTemporaryFolder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.plugin.testutils; 17 | 18 | import org.junit.rules.TemporaryFolder; 19 | 20 | import java.io.File; 21 | import java.nio.file.Files; 22 | 23 | public class PythonTemporaryFolder extends TemporaryFolder { 24 | 25 | public PythonTemporaryFolder(File parentFolder) { 26 | super(parentFolder); 27 | } 28 | 29 | @Override 30 | public void delete() { 31 | if (getRoot() != null) { 32 | deleteRecursiveIfExists(getRoot()); 33 | } 34 | } 35 | 36 | public static boolean deleteRecursiveIfExists(File item) { 37 | if (!item.exists()) { 38 | return true; 39 | } 40 | if (!Files.isSymbolicLink(item.toPath()) && item.isDirectory()) { 41 | File[] subitems = item.listFiles(); 42 | assert subitems != null; 43 | for (File subitem : subitems) { 44 | if (!deleteRecursiveIfExists(subitem)) { 45 | return false; 46 | } 47 | } 48 | } 49 | return item.delete(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /pygradle-plugin/src/integTest/groovy/com/linkedin/gradle/python/tasks/InstallVirtualEnvironmentTaskTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.tasks 17 | 18 | import com.linkedin.gradle.python.plugin.testutils.DefaultProjectLayoutRule 19 | import com.linkedin.gradle.python.plugin.testutils.PyGradleTestBuilder 20 | import org.gradle.testkit.runner.GradleRunner 21 | import org.gradle.testkit.runner.TaskOutcome 22 | import org.junit.Rule 23 | import spock.lang.Specification 24 | 25 | import java.nio.file.Paths 26 | 27 | class InstallVirtualEnvironmentTaskTest extends Specification { 28 | 29 | @Rule 30 | final DefaultProjectLayoutRule testProjectDir = new DefaultProjectLayoutRule() 31 | 32 | def "will create venv"() { 33 | given: 34 | testProjectDir.buildFile << """\ 35 | |plugins { 36 | | id 'com.linkedin.python' 37 | |} 38 | | 39 | |${PyGradleTestBuilder.createRepoClosure()} 40 | """.stripMargin().stripIndent() 41 | 42 | when: 43 | def result = GradleRunner.create() 44 | .withProjectDir(testProjectDir.root) 45 | .withArguments('createVirtualEnvironment') 46 | .withPluginClasspath() 47 | .build() 48 | println result.output 49 | 50 | then: 51 | def file = testProjectDir.getRoot().toPath().resolve(Paths.get("foo", "build", "venv")).toFile() 52 | file.exists() 53 | 54 | result.task(':foo:createVirtualEnvironment').outcome == TaskOutcome.SUCCESS 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /pygradle-plugin/src/integTest/resources/setup.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2016 LinkedIn Corp. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | setup( 18 | distclass=GradleDistribution, 19 | package_dir={'': 'src'}, 20 | packages=find_packages('src'), 21 | include_package_data=True, 22 | 23 | entry_points={ 24 | 'console_scripts': [ 25 | 'hello_world = foo.hello:main', 26 | ], 27 | } 28 | ) 29 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/checkstyle/model/FileStyleViolations.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.checkstyle.model; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | 22 | public class FileStyleViolations { 23 | private final String filename; 24 | private final List violations = new ArrayList(); 25 | 26 | FileStyleViolations(String filename) { 27 | this.filename = filename; 28 | } 29 | 30 | void addViolation(Integer lineNumber, Integer columnNumber, String errorCode, String message) { 31 | violations.add(new StyleViolation(lineNumber, columnNumber, errorCode, message)); 32 | } 33 | 34 | public String getFilename() { 35 | return filename; 36 | } 37 | 38 | public List getViolations() { 39 | return violations; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/coverage/CoverageXmlReporter.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.coverage 17 | 18 | import groovy.xml.MarkupBuilder 19 | 20 | class CoverageXmlReporter { 21 | private final String coverageInfo 22 | 23 | CoverageXmlReporter(String coverageInfo) { 24 | this.coverageInfo = coverageInfo 25 | } 26 | 27 | public String generateXML() { 28 | def xmlWriter = new StringWriter() 29 | def xmlMarkup = new MarkupBuilder(xmlWriter) 30 | def coverage = getCoverage() 31 | def missedStatements = coverage['missed_statements'] 32 | def coveredStatements = coverage['total_statements'] - coverage['missed_statements'] 33 | 34 | xmlMarkup.report(name: "coverage") { 35 | "package"(name: "coverage") { 36 | "class"(name: "coverage/coverage") { 37 | counter(type: "LINE", missed: missedStatements, covered: coveredStatements) 38 | } 39 | } 40 | } 41 | return xmlWriter.toString() 42 | } 43 | 44 | private Map getCoverage() { 45 | def group = (coverageInfo =~ /\d+/) 46 | def missedStatements = 0 47 | def totalStatements = 0 48 | 49 | if (group.size() >= 3) { 50 | totalStatements = group[0].toInteger() 51 | missedStatements = group[1].toInteger() 52 | } 53 | 54 | return [total_statements: totalStatements, missed_statements: missedStatements] 55 | } 56 | } 57 | 58 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/exception/MissingInterpreterException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.exception; 17 | 18 | /** 19 | * Failed to find Python interpreter in PATH. 20 | */ 21 | public class MissingInterpreterException extends RuntimeException { 22 | public MissingInterpreterException(String message) { 23 | super(message); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/exception/PipExecutionException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.exception; 17 | 18 | import com.linkedin.gradle.python.util.PackageInfo; 19 | import org.gradle.api.GradleException; 20 | 21 | import java.util.List; 22 | 23 | public class PipExecutionException extends GradleException { 24 | private final String pipText; 25 | 26 | private PipExecutionException(String message, String pipText) { 27 | super(message); 28 | this.pipText = pipText; 29 | } 30 | 31 | public String getPipText() { 32 | return pipText; 33 | } 34 | 35 | public static PipExecutionException failedInstall(PackageInfo packageInfo, String pipText) { 36 | String body = String.format("Failed to install %s. " 37 | + "Please see above output for reason, or re-run your build using " 38 | + "``gradle -i build`` for additional logging.", packageInfo.toShortHand()); 39 | return new PipExecutionException(body, pipText); 40 | } 41 | 42 | public static PipExecutionException failedWheel(PackageInfo packageInfo, String pipText) { 43 | String body = String.format("Failed to build wheel for %s. " 44 | + "Please see above output for reason, or re-run your build using " 45 | + "``gradle -i build`` for additional logging.", packageInfo.toShortHand()); 46 | return new PipExecutionException(body, pipText); 47 | } 48 | 49 | public static PipExecutionException unsupportedPythonVersion(PackageInfo packageInfo, List supportedVersions) { 50 | String message = String.format("Package %s works only with Python versions: %s", packageInfo.getName(), supportedVersions); 51 | return new PipExecutionException(message, message); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/extension/BlackExtension.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.extension; 17 | 18 | import com.linkedin.gradle.python.extension.internal.DefaultExternalTool; 19 | 20 | 21 | public class BlackExtension extends DefaultExternalTool { 22 | } 23 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/extension/CliExtension.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.extension; 17 | 18 | public class CliExtension { 19 | private boolean generateCompletions = false; 20 | 21 | public boolean isGenerateCompletions() { 22 | return generateCompletions; 23 | } 24 | 25 | public void setGenerateCompletions(boolean generateCompletions) { 26 | this.generateCompletions = generateCompletions; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/extension/CoverageExtension.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.extension; 17 | 18 | public class CoverageExtension { 19 | private boolean run; 20 | 21 | public boolean isRun() { 22 | return run; 23 | } 24 | 25 | public void setRun(boolean run) { 26 | this.run = run; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/extension/DeployableExtension.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.extension; 17 | 18 | import org.gradle.api.Project; 19 | 20 | import java.io.File; 21 | 22 | 23 | public class DeployableExtension { 24 | 25 | private File deployableBuildDir; 26 | private File deployableBinDir; 27 | private File deployableEtcDir; 28 | 29 | public DeployableExtension(Project project) { 30 | deployableBuildDir = new File(project.getBuildDir(), "deployable"); 31 | deployableBinDir = new File(deployableBuildDir, "bin"); 32 | deployableEtcDir = new File(deployableBuildDir, "etc"); 33 | } 34 | 35 | public File getDeployableBuildDir() { 36 | return deployableBuildDir; 37 | } 38 | 39 | public void setDeployableBuildDir(File deployableBuildDir) { 40 | this.deployableBuildDir = deployableBuildDir; 41 | } 42 | 43 | public File getDeployableBinDir() { 44 | return deployableBinDir; 45 | } 46 | 47 | public void setDeployableBinDir(File deployableBinDir) { 48 | this.deployableBinDir = deployableBinDir; 49 | } 50 | 51 | public File getDeployableEtcDir() { 52 | return deployableEtcDir; 53 | } 54 | 55 | public void setDeployableEtcDir(File deployableEtcDir) { 56 | this.deployableEtcDir = deployableEtcDir; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/extension/IsortExtension.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.extension; 17 | 18 | import com.linkedin.gradle.python.extension.internal.DefaultExternalTool; 19 | 20 | 21 | public class IsortExtension extends DefaultExternalTool { 22 | } 23 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/extension/MypyExtension.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.extension; 17 | 18 | import com.linkedin.gradle.python.extension.internal.DefaultExternalTool; 19 | 20 | 21 | public class MypyExtension extends DefaultExternalTool { 22 | } 23 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/extension/PythonDetails.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.extension; 17 | 18 | import java.io.File; 19 | import java.io.Serializable; 20 | import java.util.Collection; 21 | 22 | /** 23 | * Details about the python version 24 | */ 25 | public interface PythonDetails extends Serializable { 26 | String getVirtualEnvPrompt(); 27 | 28 | void setVirtualEnvPrompt(String virtualEnvPrompt); 29 | 30 | File getVirtualEnv(); 31 | 32 | File getVirtualEnvInterpreter(); 33 | 34 | File getSystemPythonInterpreter(); 35 | 36 | File getActivateLink(); 37 | 38 | void setActivateLink(File activateLink); 39 | 40 | void prependExecutableDirectory(File file); 41 | 42 | void appendExecutableDirectory(File file); 43 | 44 | void setPythonDefaultVersions(PythonDefaultVersions defaults); 45 | 46 | void setPythonDefaultVersions(String defaultPython2, String defaultPython3, Collection allowedVersions); 47 | 48 | PythonDefaultVersions getPythonDefaultVersions(); 49 | 50 | void setPythonVersion(String version); 51 | 52 | void setSystemPythonInterpreter(String path); 53 | 54 | PythonVersion getPythonVersion(); 55 | 56 | VirtualEnvironment getVirtualEnvironment(); 57 | } 58 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/extension/PythonDetailsFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.extension; 17 | 18 | import com.linkedin.gradle.python.extension.internal.DefaultPythonDetails; 19 | import com.linkedin.gradle.python.util.OperatingSystem; 20 | import org.gradle.api.Project; 21 | 22 | import java.io.File; 23 | 24 | /** 25 | * Intended to dispense Python Details given a project. 26 | */ 27 | public class PythonDetailsFactory { 28 | private PythonDetailsFactory() { 29 | //Private constructor for utility class 30 | } 31 | 32 | /** 33 | * Make a new PythonDetails 34 | */ 35 | public static PythonDetails makePythonDetails(Project project, File venv) { 36 | return new DefaultPythonDetails(project, venv); 37 | } 38 | 39 | /** 40 | * Make a new PythonDetails 41 | */ 42 | public static PythonDetails withNewVenv(Project project, PythonDetails fromDetails, File venv) { 43 | DefaultPythonDetails nextDetails = new DefaultPythonDetails(project, venv); 44 | nextDetails.setPythonInterpreter(fromDetails.getPythonVersion(), fromDetails.getSystemPythonInterpreter()); 45 | return nextDetails; 46 | } 47 | 48 | /** 49 | * @return The name of the "exec" dir 50 | */ 51 | public static String getPythonApplicationDirectory() { 52 | return OperatingSystem.current().isWindows() ? "Scripts" : "bin"; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/extension/PythonVersion.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.extension; 17 | 18 | import java.io.Serializable; 19 | 20 | 21 | public class PythonVersion implements Serializable { 22 | 23 | private final String version; 24 | 25 | public PythonVersion(String version) { 26 | this.version = version; 27 | } 28 | 29 | private String getVersionPart(int position) { 30 | String[] parts = version.split("\\."); 31 | return parts[position]; 32 | } 33 | 34 | /** 35 | * @return The exact version of Python this project uses, such as '2.7.11'. 36 | */ 37 | public String getPythonVersion() { 38 | return version; 39 | } 40 | 41 | /** 42 | * @return The short version of Python this project uses, such as '2.7'. 43 | */ 44 | public String getPythonMajorMinor() { 45 | return String.format("%s.%s", getVersionPart(0), getVersionPart(1)); 46 | } 47 | 48 | /** 49 | * @return The major version of Python this project uses, such as '2'. 50 | */ 51 | public String getPythonMajor() { 52 | return getVersionPart(0); 53 | } 54 | 55 | /** 56 | * @return The minor version of Python this project uses, such as '7'. 57 | */ 58 | public String getPythonMinor() { 59 | return getVersionPart(1); 60 | } 61 | 62 | /** 63 | * @return The patch version of Python this project uses, such as '11'. 64 | */ 65 | public String getPythonPatch() { 66 | return getVersionPart(2); 67 | } 68 | 69 | @Override 70 | public String toString() { 71 | return "PythonVersion{" 72 | + "version='" + version + '\'' 73 | + '}'; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/extension/PythonVersionParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.extension; 17 | 18 | import org.gradle.api.GradleException; 19 | import org.gradle.api.Project; 20 | import org.gradle.process.ExecResult; 21 | 22 | import java.io.ByteArrayOutputStream; 23 | import java.io.File; 24 | import java.util.Collections; 25 | 26 | public class PythonVersionParser { 27 | 28 | private PythonVersionParser() { 29 | // noop 30 | } 31 | 32 | public static PythonVersion parsePythonVersion(final Project project, final File pythonInterpreter) { 33 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 34 | 35 | ExecResult execResult = project.exec(execSpec -> { 36 | execSpec.setExecutable(pythonInterpreter.getAbsolutePath()); 37 | execSpec.setArgs(Collections.singletonList("--version")); 38 | execSpec.setStandardOutput(outputStream); 39 | execSpec.setErrorOutput(outputStream); 40 | execSpec.setIgnoreExitValue(true); 41 | }); 42 | 43 | String output = outputStream.toString(); 44 | if (execResult.getExitValue() != 0) { 45 | throw new GradleException(output); 46 | } 47 | 48 | String versionString = output.trim().split(" ")[1]; 49 | return new PythonVersion(versionString); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/extension/VirtualEnvironment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.extension; 17 | 18 | import java.io.File; 19 | import java.io.Serializable; 20 | 21 | /** 22 | * Description of a virtual environment 23 | */ 24 | public interface VirtualEnvironment extends Serializable { 25 | 26 | File getPip(); 27 | 28 | File getPex(); 29 | 30 | File getExecutable(String path); 31 | 32 | File getScript(String path); 33 | 34 | File findExecutable(String path); 35 | } 36 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/extension/WheelExtension.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.extension; 17 | 18 | import com.linkedin.gradle.python.wheel.WheelCacheLayer; 19 | import org.gradle.api.Project; 20 | 21 | import java.io.File; 22 | import java.util.LinkedHashMap; 23 | import java.util.Map; 24 | import java.util.Optional; 25 | 26 | 27 | public class WheelExtension { 28 | 29 | private File wheelCache; 30 | private File hostLayerWheelCache; 31 | private File projectLayerWheelCache; 32 | private Map layeredCacheMap = new LinkedHashMap<>(); 33 | 34 | public WheelExtension(Project project) { 35 | wheelCache = new File(project.getBuildDir(), "wheel-cache"); 36 | } 37 | 38 | public File getWheelCache() { 39 | return wheelCache; 40 | } 41 | 42 | public void setWheelCache(File wheelCache) { 43 | this.wheelCache = wheelCache; 44 | } 45 | 46 | public Optional getHostLayerWheelCache() { 47 | return Optional.ofNullable(hostLayerWheelCache); 48 | } 49 | 50 | public void setHostLayerWheelCache(File hostLayerWheelCache) { 51 | this.hostLayerWheelCache = hostLayerWheelCache; 52 | layeredCacheMap.put(WheelCacheLayer.HOST_LAYER, this.hostLayerWheelCache); 53 | } 54 | 55 | public Optional getProjectLayerWheelCache() { 56 | return Optional.ofNullable(projectLayerWheelCache); 57 | } 58 | 59 | public void setProjectLayerWheelCache(File projectLayerWheelCache) { 60 | this.projectLayerWheelCache = projectLayerWheelCache; 61 | layeredCacheMap.put(WheelCacheLayer.PROJECT_LAYER, this.projectLayerWheelCache); 62 | } 63 | 64 | public Map getLayeredCacheMap() { 65 | return layeredCacheMap; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/extension/ZipappContainerExtension.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.extension; 17 | 18 | import com.linkedin.gradle.python.util.OperatingSystem; 19 | 20 | 21 | /** 22 | * This class provides a stub in PythonExtension so that build.gradle files 23 | * can use the following construct: 24 | * 25 | * python { 26 | * zipapp.isFat = true 27 | * } 28 | * 29 | * This is the replacement for `python.pex.fatPex`, `python.pex.isFat`, and 30 | * `python.shiv.isFat`. The reason we need this is that we don't know which 31 | * container format the user wants until *after* build.gradle is evaluated, 32 | * but of course they want to set the isFat flag *in* their build.gradle. 33 | */ 34 | public class ZipappContainerExtension { 35 | private boolean isFat = OperatingSystem.current().isWindows(); 36 | 37 | public boolean isFat() { 38 | return isFat; 39 | } 40 | 41 | public void setIsFat(boolean isFat) { 42 | this.isFat = isFat; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/extension/internal/DefaultExternalTool.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.extension.internal; 17 | 18 | 19 | public class DefaultExternalTool { 20 | private boolean run; 21 | private String[] arguments = null; 22 | 23 | public boolean isRun() { 24 | return run; 25 | } 26 | 27 | public void setRun(boolean run) { 28 | this.run = run; 29 | } 30 | 31 | public void setArguments(String argumentString) { 32 | arguments = argumentString.split("\\s+"); 33 | } 34 | 35 | public String[] getArguments() { 36 | return arguments; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/plugin/PyGradleDependencyResolveDetails.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.plugin; 17 | 18 | import org.gradle.api.Action; 19 | import org.gradle.api.artifacts.DependencyResolveDetails; 20 | import org.gradle.api.logging.Logger; 21 | import org.gradle.api.logging.Logging; 22 | 23 | import java.util.Map; 24 | import java.util.Objects; 25 | 26 | 27 | public class PyGradleDependencyResolveDetails implements Action { 28 | 29 | private static final Logger LOGGER = Logging.getLogger(PyGradleDependencyResolveDetails.class); 30 | 31 | private final Map> requiredVersions; 32 | 33 | public PyGradleDependencyResolveDetails(Map> requiredVersions) { 34 | this.requiredVersions = requiredVersions; 35 | } 36 | 37 | @Override 38 | public void execute(DependencyResolveDetails details) { 39 | if (requiredVersions.containsKey(details.getRequested().getName())) { 40 | String name = details.getRequested().getName(); 41 | String version = requiredVersions.get(name).get("version"); 42 | if (Objects.equals("", version) || null == version) { 43 | return; 44 | } 45 | LOGGER.info("Resolving {} to {}=={} per gradle-python resolution strategy.", name, name, version); 46 | details.useVersion(version); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/plugin/PythonBasePlugin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.plugin; 17 | 18 | import com.linkedin.gradle.python.PythonExtension; 19 | import org.gradle.api.Plugin; 20 | import org.gradle.api.Project; 21 | 22 | public abstract class PythonBasePlugin implements Plugin { 23 | 24 | public PythonExtension settings; 25 | public Project project; 26 | 27 | @Override 28 | public void apply(Project target) { 29 | this.project = target; 30 | target.getPlugins().apply(PythonPlugin.class); 31 | settings = project.getExtensions().getByType(PythonExtension.class); 32 | applyTo(target); 33 | } 34 | 35 | public abstract void applyTo(Project project); 36 | } 37 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/plugin/PythonCliDistributionPlugin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.plugin; 17 | 18 | import com.linkedin.gradle.python.util.ApplicationContainer; 19 | import com.linkedin.gradle.python.tasks.GenerateCompletionsTask; 20 | import com.linkedin.gradle.python.util.ExtensionUtils; 21 | import com.linkedin.gradle.python.util.StandardTextValues; 22 | import org.gradle.api.Project; 23 | import org.gradle.api.tasks.TaskContainer; 24 | 25 | 26 | public class PythonCliDistributionPlugin extends PythonContainerPlugin { 27 | 28 | public static final String TASK_GENERATE_COMPLETIONS = "generateCompletions"; 29 | 30 | @Override 31 | public void applyTo(Project project) { 32 | project.getPlugins().apply(PythonContainerPlugin.class); 33 | ExtensionUtils.maybeCreateCliExtension(project); 34 | 35 | TaskContainer tasks = project.getTasks(); 36 | 37 | GenerateCompletionsTask completionsTask = tasks.create(TASK_GENERATE_COMPLETIONS, GenerateCompletionsTask.class); 38 | completionsTask.dependsOn(tasks.getByName(StandardTextValues.TASK_INSTALL_PROJECT.getValue())); 39 | 40 | tasks.getByName(ApplicationContainer.TASK_ASSEMBLE_CONTAINERS) 41 | .dependsOn(project.getTasks().getByName(TASK_GENERATE_COMPLETIONS)); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/plugin/PythonHelpers.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.plugin; 17 | 18 | import org.gradle.api.Project; 19 | import org.gradle.api.logging.configuration.ConsoleOutput; 20 | 21 | public class PythonHelpers { 22 | 23 | public static final int LINE_WIDTH = 80; 24 | 25 | private PythonHelpers() { 26 | // noop 27 | } 28 | 29 | public static boolean isPlainOrVerbose(Project project) { 30 | return project.getLogger().isInfoEnabled() || System.getenv("TERM") == null 31 | || project.getGradle().getStartParameter().getConsoleOutput() == ConsoleOutput.Plain; 32 | } 33 | 34 | public static String createPrettyLine(String prefix, String postfix) { 35 | StringBuilder successFlair = new StringBuilder(); 36 | 37 | 38 | successFlair.append(prefix).append(" "); 39 | for (int i = 0; i < LINE_WIDTH - 3 - prefix.length() - postfix.length(); i++) { 40 | successFlair.append("."); 41 | } 42 | 43 | successFlair.append("."); 44 | successFlair.append(" ").append(postfix); 45 | 46 | return successFlair.toString(); 47 | 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/plugin/PythonPexDistributionPlugin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.plugin; 17 | 18 | import com.linkedin.gradle.python.PythonExtension; 19 | import com.linkedin.gradle.python.util.ExtensionUtils; 20 | import org.gradle.api.Project; 21 | 22 | 23 | public class PythonPexDistributionPlugin extends PythonContainerPlugin { 24 | @Override 25 | public void applyTo(final Project project) { 26 | final PythonExtension pythonExtension = ExtensionUtils.getPythonExtension(project); 27 | 28 | // Even though this is the default, explicit is better than implicit. 29 | pythonExtension.setContainer("pex"); 30 | super.applyTo(project); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/plugin/PythonSourceDistributionPlugin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.plugin; 17 | 18 | import com.linkedin.gradle.python.tasks.SourceDistTask; 19 | import com.linkedin.gradle.python.util.StandardTextValues; 20 | import org.gradle.api.Project; 21 | 22 | import java.util.LinkedHashMap; 23 | 24 | public class PythonSourceDistributionPlugin extends PythonBasePlugin { 25 | 26 | public static final String TASK_PACKAGE_SDIST = "packageSdist"; 27 | 28 | /** 29 | * Create a Python source distribution. 30 | */ 31 | @Override 32 | public void applyTo(final Project project) { 33 | 34 | SourceDistTask sdistPackageTask = project.getTasks().create(TASK_PACKAGE_SDIST, SourceDistTask.class, 35 | task -> task.dependsOn(project.getTasks().getByName(StandardTextValues.TASK_INSTALL_PROJECT.getValue()))); 36 | 37 | LinkedHashMap sdistArtifactInfo = new LinkedHashMap<>(5); 38 | sdistArtifactInfo.put("name", project.getName()); 39 | sdistArtifactInfo.put("type", "tgz"); 40 | sdistArtifactInfo.put("extension", "tar.gz"); 41 | sdistArtifactInfo.put("file", sdistPackageTask.getSdistOutput()); 42 | sdistArtifactInfo.put("builtBy", project.getTasks().getByName(TASK_PACKAGE_SDIST)); 43 | 44 | project.getArtifacts().add(StandardTextValues.CONFIGURATION_DEFAULT.getValue(), sdistArtifactInfo); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/AbstractPythonTestSourceDefaultTask.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.tasks; 17 | 18 | import org.gradle.api.file.ConfigurableFileTree; 19 | import org.gradle.api.file.FileCollection; 20 | import org.gradle.api.file.FileTree; 21 | import org.gradle.api.tasks.InputFiles; 22 | 23 | 24 | /** 25 | * Add's lazy evaluation of the tests directory, see {@link AbstractPythonMainSourceDefaultTask} for more details. 26 | */ 27 | abstract public class AbstractPythonTestSourceDefaultTask extends AbstractPythonMainSourceDefaultTask { 28 | 29 | FileTree testSource; 30 | 31 | AbstractPythonTestSourceDefaultTask() { 32 | } 33 | 34 | @InputFiles 35 | FileCollection getTestFiles() { 36 | ConfigurableFileTree componentFiles = getProject().fileTree(getPythonExtension().testDir); 37 | componentFiles.exclude(standardExcludes()); 38 | if (testSource != null) { 39 | return testSource.plus(componentFiles); 40 | } 41 | return componentFiles; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/BlackTask.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.tasks; 17 | 18 | import com.linkedin.gradle.python.extension.BlackExtension; 19 | import com.linkedin.gradle.python.extension.PythonDetails; 20 | import com.linkedin.gradle.python.util.ExtensionUtils; 21 | import org.gradle.api.Project; 22 | import org.gradle.process.ExecResult; 23 | 24 | 25 | public class BlackTask extends AbstractPythonMainSourceDefaultTask { 26 | 27 | public void preExecution() { 28 | PythonDetails blackDetails = getPythonDetails(); 29 | args(blackDetails.getVirtualEnvironment().findExecutable("black").getAbsolutePath()); 30 | 31 | Project project = getProject(); 32 | BlackExtension black = ExtensionUtils.getPythonComponentExtension(project, BlackExtension.class); 33 | 34 | String[] arguments = black.getArguments(); 35 | 36 | if (arguments == null) { 37 | // Default to longer line length (160) than the default (88) 38 | // Default to check only 39 | arguments = new String[] {"--check", "-l", "160", getPythonExtension().srcDir, getPythonExtension().testDir}; 40 | } 41 | args(arguments); 42 | } 43 | 44 | public void processResults(ExecResult results) { 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/CleanSaveVenvTask.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.tasks; 17 | 18 | import org.gradle.api.DefaultTask; 19 | import org.gradle.api.tasks.TaskAction; 20 | 21 | import java.io.File; 22 | 23 | 24 | public class CleanSaveVenvTask extends DefaultTask { 25 | 26 | @TaskAction 27 | public void cleanProject() throws Exception { 28 | File buildDir = getProject().getBuildDir(); 29 | File[] directoryListing = buildDir.listFiles(); 30 | if (directoryListing != null) { 31 | for (File f : directoryListing) { 32 | if (!f.getName().equals("venv")) { 33 | f.delete(); 34 | } 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/GenerateCompletionsTask.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.tasks 17 | 18 | import com.linkedin.gradle.python.extension.CliExtension 19 | import com.linkedin.gradle.python.extension.DeployableExtension 20 | import com.linkedin.gradle.python.util.ExtensionUtils 21 | import groovy.transform.CompileStatic 22 | import org.gradle.api.tasks.OutputDirectory 23 | import org.gradle.api.tasks.StopActionException 24 | import org.gradle.process.ExecResult 25 | import org.gradle.process.ExecSpec 26 | 27 | /** 28 | * Generates completion files. 29 | */ 30 | @CompileStatic 31 | class GenerateCompletionsTask extends AbstractPythonMainSourceDefaultTask { 32 | 33 | @OutputDirectory 34 | File getEtcDir() { 35 | def deployableExtension = ExtensionUtils.getPythonComponentExtension(project, DeployableExtension) 36 | return deployableExtension.deployableEtcDir 37 | } 38 | 39 | @Override 40 | public void processResults(ExecResult execResult) { 41 | execResult.assertNormalExitValue() 42 | } 43 | 44 | public void preExecution() { 45 | if (!(ExtensionUtils.findPythonComponentExtension(project, CliExtension)?.generateCompletions)) { 46 | throw new StopActionException() 47 | } 48 | 49 | String completionScript = getClass().getResource('/templates/click_tabtab.py').text 50 | File.createTempFile('click_tabtab', '.py').with { 51 | deleteOnExit() 52 | write(completionScript) 53 | args(absolutePath) 54 | } 55 | getProject().file(getEtcDir()).mkdirs() 56 | } 57 | 58 | public void configureExecution(ExecSpec execSpec) { 59 | execSpec.setWorkingDir(getEtcDir()) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/GenerateSetupPyTask.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.tasks; 17 | 18 | import org.apache.commons.io.FileUtils; 19 | import org.apache.commons.io.IOUtils; 20 | import org.gradle.api.DefaultTask; 21 | import org.gradle.api.logging.Logger; 22 | import org.gradle.api.logging.Logging; 23 | import org.gradle.api.tasks.TaskAction; 24 | 25 | import java.io.File; 26 | import java.io.IOException; 27 | 28 | public class GenerateSetupPyTask extends DefaultTask { 29 | 30 | private static final Logger logger = Logging.getLogger(GenerateSetupPyTask.class); 31 | 32 | public GenerateSetupPyTask() { 33 | setDescription("Writes the suggested setup.py out to disk. This will overwrite any existing setup.py"); 34 | setGroup("documentation"); 35 | } 36 | 37 | @TaskAction 38 | public void createSetupPy() throws IOException { 39 | File file = getProject().file("setup.py"); 40 | if (file.exists()) { 41 | logger.lifecycle("Contents of setup.py are going to be overwritten!!"); 42 | file.delete(); 43 | } 44 | file.createNewFile(); 45 | 46 | String setupPy = IOUtils.toString(GenerateSetupPyTask.class.getResourceAsStream("/templates/setup.py.template")); 47 | FileUtils.write(file, setupPy); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/GetProbedTagsTask.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.tasks; 17 | 18 | import com.linkedin.gradle.python.extension.PythonDetails; 19 | import com.linkedin.gradle.python.tasks.action.ProbeVenvInfoAction; 20 | import com.linkedin.gradle.python.tasks.provides.ProvidesVenv; 21 | import com.linkedin.gradle.python.wheel.EditablePythonAbiContainer; 22 | import org.gradle.api.DefaultTask; 23 | import org.gradle.api.tasks.TaskAction; 24 | 25 | 26 | /** 27 | * Get supported wheel tags from previously probed virtual environment. 28 | * 29 | *

When virtual environment creation task is up-to-date, 30 | * then ABI container does not get populated, remains empty, and 31 | * no cached wheels can match any tags. This task ensures that 32 | * the container does get populated, regardless of virtual environment 33 | * creation task outcome.

34 | */ 35 | public class GetProbedTagsTask extends DefaultTask implements ProvidesVenv { 36 | private PythonDetails pythonDetails; 37 | private EditablePythonAbiContainer editablePythonAbiContainer; 38 | 39 | @TaskAction 40 | public void getProbedTags() { 41 | ProbeVenvInfoAction.getProbedTags(getProject(), pythonDetails, editablePythonAbiContainer); 42 | } 43 | 44 | @Override 45 | public void setEditablePythonAbiContainer(EditablePythonAbiContainer editablePythonAbiContainer) { 46 | this.editablePythonAbiContainer = editablePythonAbiContainer; 47 | } 48 | 49 | public PythonDetails getPythonDetails() { 50 | return pythonDetails; 51 | } 52 | 53 | public void setPythonDetails(PythonDetails pythonDetails) { 54 | this.pythonDetails = pythonDetails; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/IsortTask.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.tasks; 17 | 18 | import com.linkedin.gradle.python.extension.IsortExtension; 19 | import com.linkedin.gradle.python.extension.PythonDetails; 20 | import com.linkedin.gradle.python.util.ExtensionUtils; 21 | import org.gradle.api.Project; 22 | import org.gradle.process.ExecResult; 23 | 24 | 25 | public class IsortTask extends AbstractPythonMainSourceDefaultTask { 26 | 27 | public void preExecution() { 28 | PythonDetails isortDetails = getPythonDetails(); 29 | args(isortDetails.getVirtualEnvironment().findExecutable("isort").getAbsolutePath()); 30 | 31 | Project project = getProject(); 32 | IsortExtension isort = ExtensionUtils.getPythonComponentExtension(project, IsortExtension.class); 33 | 34 | String[] arguments = isort.getArguments(); 35 | 36 | if (arguments == null) { 37 | // Default to --check-only --recursive src/ test/ 38 | arguments = new String[]{"--check-only", "--recursive", getPythonExtension().srcDir, getPythonExtension().testDir}; 39 | } 40 | args(arguments); 41 | } 42 | 43 | public void processResults(ExecResult results) { 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/MypyTask.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.tasks; 17 | 18 | import com.linkedin.gradle.python.extension.MypyExtension; 19 | import com.linkedin.gradle.python.extension.PythonDetails; 20 | import com.linkedin.gradle.python.util.ExtensionUtils; 21 | import org.gradle.api.Project; 22 | import org.gradle.process.ExecResult; 23 | 24 | 25 | public class MypyTask extends AbstractPythonMainSourceDefaultTask { 26 | 27 | public void preExecution() { 28 | PythonDetails mypyDetails = getPythonDetails(); 29 | args(mypyDetails.getVirtualEnvironment().findExecutable("mypy").getAbsolutePath()); 30 | 31 | Project project = getProject(); 32 | MypyExtension mypy = ExtensionUtils.getPythonComponentExtension(project, MypyExtension.class); 33 | 34 | String[] arguments = mypy.getArguments(); 35 | if (arguments == null) { 36 | arguments = new String[]{getPythonExtension().srcDir}; 37 | } 38 | args(arguments); 39 | } 40 | 41 | public void processResults(ExecResult results) { 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/NoopBuildPexTask.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.tasks; 17 | 18 | import org.gradle.api.DefaultTask; 19 | import org.gradle.api.Task; 20 | import org.gradle.api.logging.Logger; 21 | import org.gradle.api.logging.Logging; 22 | import org.gradle.api.tasks.TaskAction; 23 | 24 | 25 | public class NoopBuildPexTask extends DefaultTask implements PythonContainerTask, NoopTask { 26 | private static final Logger LOG = Logging.getLogger(NoopBuildPexTask.class); 27 | private static final String DISABLED_MESSAGE = 28 | "######################### WARNING ##########################\n" 29 | + "The buildPex task has been deprecated.\n" 30 | + "Please use the assembleContainers task instead.\n" 31 | + "############################################################"; 32 | 33 | // This is used to suppress the warning when PythonContainerPlugin plumbs 34 | // this task into the task hierarchy, which isn't user code. 35 | private boolean suppressWarning = false; 36 | 37 | @TaskAction 38 | public void noOp() { } 39 | 40 | public Task dependsOn(Object... paths) { 41 | if (!suppressWarning) { 42 | LOG.warn(DISABLED_MESSAGE); 43 | } 44 | return super.dependsOn(paths); 45 | } 46 | 47 | public boolean suppressWarning() { 48 | return suppressWarning; 49 | } 50 | 51 | public void setSuppressWarning(boolean suppressWarning) { 52 | this.suppressWarning = suppressWarning; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/NoopTask.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linkedin.gradle.python.tasks; 18 | 19 | 20 | public interface NoopTask { 21 | public boolean suppressWarning(); 22 | 23 | public void setSuppressWarning(boolean warning); 24 | } 25 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/PythonContainerTask.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linkedin.gradle.python.tasks; 18 | 19 | import org.gradle.api.Task; 20 | 21 | 22 | public interface PythonContainerTask extends Task { 23 | } 24 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/SourceDistTask.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.tasks; 17 | 18 | import org.gradle.api.Project; 19 | import org.gradle.api.tasks.OutputFile; 20 | import org.gradle.process.ExecResult; 21 | 22 | import java.io.File; 23 | 24 | 25 | public class SourceDistTask extends AbstractPythonMainSourceDefaultTask { 26 | 27 | public SourceDistTask() { 28 | args("setup.py", "sdist", "--dist-dir", getDistDir().getAbsolutePath()); 29 | } 30 | 31 | @Override 32 | public void processResults(ExecResult execResult) { 33 | } 34 | 35 | @OutputFile 36 | public File getSdistOutput() { 37 | Project project = getProject(); 38 | return new File( 39 | getDistDir(), 40 | String.format( 41 | "%s-%s.tar.gz", 42 | project.getName(), 43 | // TODO: Is this replace here really necessary? 44 | project.getVersion().toString().replace("_", "-"))); 45 | } 46 | 47 | private File getDistDir() { 48 | return new File(getProject().getBuildDir(), "distributions"); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/SphinxDocumentationTask.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.tasks 17 | 18 | import groovy.transform.CompileStatic 19 | import org.gradle.api.file.FileCollection 20 | import org.gradle.api.tasks.Input 21 | import org.gradle.api.tasks.InputFiles 22 | import org.gradle.api.tasks.OutputDirectory 23 | import org.gradle.process.ExecResult 24 | 25 | /** 26 | * Generates Sphinx Documentation 27 | */ 28 | @CompileStatic 29 | class SphinxDocumentationTask extends AbstractPythonMainSourceDefaultTask { 30 | 31 | @InputFiles 32 | FileCollection getDocFiles() { 33 | return getProject().fileTree(getComponent().docsDir) 34 | } 35 | 36 | @OutputDirectory 37 | File getDocDir() { 38 | def typeString = type.toString().toLowerCase() 39 | return new File(project.buildDir, "docs/${ typeString }/${ project.name }-${ -> project.version }-docs-${ typeString }") 40 | } 41 | 42 | /** 43 | * The type of documentation that will be generated 44 | */ 45 | @Input 46 | public DocType type 47 | 48 | @Override 49 | void preExecution() { 50 | args(pythonDetails.virtualEnvironment.findExecutable('sphinx-build').absolutePath, 51 | '-b', type.builderName, 52 | project.file(component.docsDir).getAbsolutePath(), 53 | "${ getDocDir().getAbsolutePath() }") 54 | } 55 | 56 | @Override 57 | void processResults(ExecResult execResult) { 58 | } 59 | 60 | /** 61 | * Types of documentation that are supported by Sphinx 62 | */ 63 | enum DocType { 64 | JSON('json'), 65 | HTML('html') 66 | 67 | final String builderName 68 | 69 | DocType(String name) { 70 | builderName = name 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/exec/ExternalExec.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.tasks.exec; 17 | 18 | import org.gradle.api.Action; 19 | import org.gradle.process.ExecResult; 20 | import org.gradle.process.ExecSpec; 21 | 22 | public interface ExternalExec { 23 | ExecResult exec(Action action); 24 | } 25 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/exec/ProjectExternalExec.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.tasks.exec; 17 | 18 | import org.gradle.api.Action; 19 | import org.gradle.api.Project; 20 | import org.gradle.process.ExecResult; 21 | import org.gradle.process.ExecSpec; 22 | 23 | public class ProjectExternalExec implements ExternalExec { 24 | 25 | private final Project project; 26 | 27 | public ProjectExternalExec(Project project) { 28 | this.project = project; 29 | } 30 | 31 | @Override 32 | public ExecResult exec(Action action) { 33 | return project.exec(action); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/execution/FailureReasonProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.tasks.execution; 17 | 18 | /** 19 | * Used by tasks that will provide output messages when something goes wrong. 20 | *

21 | * This is useful for collecting metric information. 22 | */ 23 | public interface FailureReasonProvider { 24 | 25 | /** 26 | * @return the message for what went wrong 27 | */ 28 | String getReason(); 29 | } 30 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/execution/TeeOutputContainer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.tasks.execution; 17 | 18 | import org.apache.commons.io.output.TeeOutputStream; 19 | import org.gradle.process.ExecSpec; 20 | 21 | import java.io.ByteArrayOutputStream; 22 | import java.io.OutputStream; 23 | 24 | /** 25 | * Collects all the output of commands so they can be used elsewhere 26 | */ 27 | public class TeeOutputContainer { 28 | 29 | private final ByteArrayOutputStream mergedStream = new ByteArrayOutputStream(); 30 | private final OutputStream teeStdOut; 31 | private final OutputStream teeErrOut; 32 | 33 | public TeeOutputContainer(OutputStream stdOut, OutputStream errOut) { 34 | teeStdOut = new TeeOutputStream(stdOut, mergedStream); 35 | teeErrOut = new TeeOutputStream(errOut, mergedStream); 36 | } 37 | 38 | public TeeOutputContainer() { 39 | this(System.out, System.err); 40 | } 41 | 42 | 43 | public void setOutputs(ExecSpec execSpec) { 44 | execSpec.setStandardOutput(teeStdOut); 45 | execSpec.setErrorOutput(teeErrOut); 46 | } 47 | 48 | public String getCommandOutput() { 49 | return mergedStream.toString(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/provides/ProvidesVenv.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.tasks.provides; 17 | 18 | import com.linkedin.gradle.python.wheel.EditablePythonAbiContainer; 19 | import org.gradle.api.Task; 20 | 21 | /** 22 | * Marker interface for tasks that make a Venv. 23 | * 24 | * This is used so that we can track venv's and the Python ABI they support 25 | */ 26 | public interface ProvidesVenv extends Task { 27 | /** 28 | * The PythonAbiContainer 29 | */ 30 | void setEditablePythonAbiContainer(EditablePythonAbiContainer editablePythonAbiContainer); 31 | } 32 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/supports/HasPythonDetails.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.tasks.supports; 17 | 18 | import com.linkedin.gradle.python.extension.PythonDetails; 19 | import org.gradle.api.Task; 20 | 21 | /** 22 | * Used to denote that a task has access to a PythonDetails object. 23 | */ 24 | public interface HasPythonDetails extends Task { 25 | 26 | /** 27 | * @return A reference to the Python Details object 28 | */ 29 | PythonDetails getPythonDetails(); 30 | } 31 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/supports/SupportsDistutilsCfg.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.tasks.supports; 17 | 18 | import org.gradle.api.Task; 19 | 20 | /** 21 | * A way to use tasks.withType for tasks that support Distutils mutations 22 | */ 23 | public interface SupportsDistutilsCfg extends Task { 24 | 25 | /** 26 | * Set the dist utils text 27 | * @param cfg string to be added to the end of the distutils file 28 | */ 29 | void setDistutilsCfg(String cfg); 30 | 31 | /** 32 | * @return Get the string to be appended to distutils config file 33 | */ 34 | String getDistutilsCfg(); 35 | } 36 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/supports/SupportsLayeredWheelCache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.tasks.supports; 17 | 18 | import com.linkedin.gradle.python.wheel.WheelCache; 19 | import org.gradle.api.Task; 20 | 21 | 22 | /** 23 | * Marker interface for tasks that support layered WheelCache 24 | */ 25 | public interface SupportsLayeredWheelCache extends Task { 26 | /** 27 | * Sets the project layer WheelCache instance. 28 | * 29 | * @param wheelCache 30 | */ 31 | void setProjectLayerWheelCache(WheelCache wheelCache); 32 | 33 | /** 34 | * Sets the host layer WheelCache instance. 35 | * 36 | * @param wheelCache 37 | */ 38 | void setHostLayerWheelCache(WheelCache wheelCache); 39 | 40 | /** 41 | * @return the project layer WheelCache instance. 42 | */ 43 | WheelCache getProjectLayerWheelCache(); 44 | 45 | /** 46 | * @return the host layer WheelCache instance. 47 | */ 48 | WheelCache getHostLayerWheelCache(); 49 | } 50 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/supports/SupportsPackageFiltering.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.tasks.supports; 17 | 18 | import com.linkedin.gradle.python.util.PackageInfo; 19 | import org.gradle.api.Task; 20 | import org.gradle.api.specs.Spec; 21 | 22 | import javax.annotation.Nullable; 23 | 24 | /** 25 | * Interface to mark tasks that support package filtering. 26 | */ 27 | public interface SupportsPackageFiltering extends HasPythonDetails, Task { 28 | 29 | /** 30 | * @return the filter, may be null. 31 | */ 32 | @Nullable 33 | Spec getPackageExcludeFilter(); 34 | 35 | /** 36 | * Set the package filter 37 | */ 38 | void setPackageExcludeFilter(Spec filter); 39 | } 40 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/supports/SupportsPackageInfoSettings.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.tasks.supports; 17 | 18 | import com.linkedin.gradle.python.util.PackageInfo; 19 | import com.linkedin.gradle.python.util.PackageSettings; 20 | import org.gradle.api.Task; 21 | 22 | /** 23 | * Marker interface for tasks that support PackageSettings 24 | */ 25 | public interface SupportsPackageInfoSettings extends Task { 26 | 27 | /** 28 | * Set the package settings 29 | */ 30 | void setPackageSettings(PackageSettings settings); 31 | 32 | /** 33 | * Get the package settings 34 | * 35 | * @return package settings 36 | */ 37 | PackageSettings getPackageSettings(); 38 | } 39 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/tasks/supports/SupportsWheelCache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.tasks.supports; 17 | 18 | import com.linkedin.gradle.python.wheel.WheelCache; 19 | import org.gradle.api.Task; 20 | 21 | /** 22 | * Marker interface for tasks that support WheelCache 23 | */ 24 | public interface SupportsWheelCache extends Task { 25 | void setWheelCache(WheelCache wheelCache); 26 | 27 | WheelCache getWheelCache(); 28 | } 29 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/util/ApplicationContainer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linkedin.gradle.python.util; 18 | 19 | import org.gradle.api.Project; 20 | 21 | 22 | public interface ApplicationContainer { 23 | public static final String TASK_BUILD_PROJECT_WHEEL = "buildProjectWheel"; 24 | public static final String TASK_BUILD_WHEELS = "buildWheels"; 25 | public static final String TASK_PACKAGE_DEPLOYABLE = "packageDeployable"; 26 | public static final String TASK_ASSEMBLE_CONTAINERS = "assembleContainers"; 27 | 28 | /** 29 | * Add any extensions that your container exposes. This runs when the 30 | * plugin is applied. 31 | */ 32 | public void addExtensions(Project project); 33 | 34 | /** 35 | * Add any additional dependencies your application container format 36 | * needs. This runs after the project is evaluated. 37 | */ 38 | public void addDependencies(Project project); 39 | 40 | /** 41 | * Create any additional tasks the extension needs. Such tasks must 42 | * implement the PythonContainerTask interface. All tasks implementing 43 | * this interface will automatically be inserted into the task dependency 44 | * graph. Implementers of this interface should *not* explicitly add 45 | * their tasks to the graph. This runs after the project is evaluated. 46 | */ 47 | public void makeTasks(Project project); 48 | } 49 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/util/DefaultEnvironmentMerger.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.util; 17 | 18 | import java.util.HashMap; 19 | import java.util.List; 20 | import java.util.Map; 21 | 22 | /** 23 | * Default implementation of EnvironmentMerger. 24 | * 25 | * Just updates maps directly. 26 | */ 27 | public class DefaultEnvironmentMerger implements EnvironmentMerger { 28 | @Override 29 | public void mergeIntoEnvironment(Map target, Map source) { 30 | if (source != null) { 31 | target.putAll(source); 32 | } 33 | } 34 | 35 | @Override 36 | public Map mergeEnvironments(List> sources) { 37 | Map target = new HashMap<>(); 38 | if (sources != null) { 39 | for (Map source : sources) { 40 | mergeIntoEnvironment(target, source); 41 | } 42 | } 43 | return target; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/util/EntryPointHelpers.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.util 17 | 18 | import com.linkedin.gradle.python.PythonExtension 19 | import groovy.transform.TypeChecked 20 | import org.gradle.api.Project 21 | import org.gradle.process.ExecSpec 22 | 23 | @TypeChecked 24 | public class EntryPointHelpers { 25 | 26 | private EntryPointHelpers() { 27 | //private constructor for util class 28 | } 29 | 30 | /** 31 | * Run ``setup.py entrypoints`` and return the results. 32 | * 33 | * The ``entrypoints`` setuptools command is a custom command provided by the distgradle.GradleDistribution 34 | * distribution class. Only classes that use this distribution class support this command. 35 | * 36 | * @param project The project to run ``setup.py entrypoints`` within. 37 | * @return A list of setuptools entry point strings that looks like ['foo = module1.module2:main', ...]. 38 | */ 39 | static public List collectEntryPoints(Project project) { 40 | PythonExtension settings = project.getExtensions().getByType(PythonExtension) 41 | def entryPointsBuf = new ByteArrayOutputStream() 42 | project.exec { ExecSpec exec -> 43 | exec.environment settings.pythonEnvironment + settings.pythonEnvironmentDistgradle 44 | exec.commandLine([ 45 | settings.details.getVirtualEnvInterpreter().absolutePath, 46 | 'setup.py', 47 | 'entrypoints', 48 | ]) 49 | exec.standardOutput = entryPointsBuf 50 | } 51 | def entryPoints = [] 52 | entryPointsBuf.toString().split(System.getProperty("line.separator")).each { 53 | if (it != 'running entrypoints') { 54 | entryPoints << it 55 | } 56 | } 57 | return entryPoints 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/util/EnvironmentMerger.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.util; 17 | 18 | import java.util.List; 19 | import java.util.Map; 20 | 21 | 22 | /** 23 | * Interface for environment merging utility. 24 | */ 25 | public interface EnvironmentMerger { 26 | /** 27 | * Merge source environment into the target environment. 28 | * 29 | * @param target the target environment to merge into 30 | * @param source the source environment to merge 31 | */ 32 | void mergeIntoEnvironment(Map target, Map source); 33 | 34 | /** 35 | * Merge source environments together. 36 | * 37 | * @param sources the source environments to merge 38 | * @return the environment with sources merged in 39 | */ 40 | Map mergeEnvironments(List> sources); 41 | } 42 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/util/FileSystemUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linkedin.gradle.python.util; 18 | 19 | import java.io.File; 20 | import java.io.IOException; 21 | import java.io.UncheckedIOException; 22 | import java.nio.file.Files; 23 | 24 | public class FileSystemUtils { 25 | 26 | private FileSystemUtils() { 27 | //private constructor for util class 28 | } 29 | 30 | /** 31 | * Make a link using the system's ``ln`` command. 32 | * 33 | * @param target The target directory that the link points to. 34 | * @param destination The destination directory or the name of the link. 35 | * @throws IOException if symlink can't be made 36 | */ 37 | public static void makeSymLink(File target, File destination) throws IOException { 38 | /* 39 | * Check if the file exists because the link checking logic in Gradle differs 40 | * between Linux and OS X machines. 41 | */ 42 | if (OperatingSystem.current().isUnix()) { 43 | if (!Files.exists(destination.toPath())) { 44 | Files.createSymbolicLink(destination.toPath(), target.toPath()); 45 | } 46 | } else { 47 | if (!Files.exists(destination.toPath())) { 48 | Files.copy(target.toPath(), destination.toPath()); 49 | } 50 | } 51 | } 52 | 53 | public static void makeSymLinkUnchecked(File target, File destination) { 54 | try { 55 | makeSymLink(target, destination); 56 | } catch (IOException e) { 57 | throw new UncheckedIOException(e); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/util/NoopOutputStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.util; 17 | 18 | import java.io.OutputStream; 19 | 20 | public class NoopOutputStream extends OutputStream { 21 | @Override 22 | public void write(int b) { 23 | // noop 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/util/PexFileUtil.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.util 17 | 18 | 19 | class PexFileUtil { 20 | 21 | private PexFileUtil() { 22 | //private constructor for util class 23 | } 24 | 25 | static String createThinPexFilename(String name) { 26 | if (OperatingSystem.current().isWindows()) { 27 | return name + ".py" 28 | } else { 29 | return name + ".pex" 30 | } 31 | } 32 | 33 | static String createFatPexFilename(String name) { 34 | if (OperatingSystem.current().isWindows()) { 35 | return name + ".py" 36 | } else { 37 | return name 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/util/entrypoint/EntryPointWriter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.util.entrypoint; 17 | 18 | import com.linkedin.gradle.python.extension.CliExtension; 19 | import com.linkedin.gradle.python.extension.ZipappContainerExtension; 20 | import com.linkedin.gradle.python.util.ExtensionUtils; 21 | import groovy.text.SimpleTemplateEngine; 22 | import org.apache.commons.io.FileUtils; 23 | import org.gradle.api.Project; 24 | 25 | import java.io.File; 26 | import java.io.IOException; 27 | import java.util.Map; 28 | 29 | 30 | public class EntryPointWriter { 31 | 32 | private final String template; 33 | private final boolean isCliTool; 34 | private final boolean isZipapp; 35 | 36 | public EntryPointWriter(Project project, String template) { 37 | this.template = template; 38 | 39 | this.isCliTool = ExtensionUtils.findPythonComponentExtension(project, CliExtension.class) != null; 40 | this.isZipapp = ExtensionUtils.findPythonComponentExtension(project, ZipappContainerExtension.class) != null; 41 | } 42 | 43 | public void writeEntryPoint(File location, Map properties) throws IOException, ClassNotFoundException { 44 | if (location.exists()) { 45 | location.delete(); 46 | } 47 | 48 | location.createNewFile(); 49 | 50 | SimpleTemplateEngine simpleTemplateEngine = new SimpleTemplateEngine(); 51 | String rendered = simpleTemplateEngine.createTemplate(template).make(properties).toString(); 52 | FileUtils.write(location, rendered); 53 | 54 | if (isCliTool || isZipapp) { 55 | location.setExecutable(true, false); 56 | location.setReadable(true, false); 57 | } else { 58 | location.setExecutable(true); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/util/internal/PyPiRepoUtil.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.util.internal 17 | 18 | import org.gradle.api.Action 19 | import org.gradle.api.Project 20 | import org.gradle.api.artifacts.repositories.IvyArtifactRepository 21 | import org.gradle.api.artifacts.repositories.IvyPatternRepositoryLayout 22 | 23 | public class PyPiRepoUtil { 24 | 25 | private PyPiRepoUtil() { 26 | // noop 27 | } 28 | 29 | public static void setupPyGradleRepo(Project project) { 30 | project.getRepositories().metaClass.pyGradlePyPi = { -> 31 | delegate.ivy(new Action() { 32 | @Override 33 | void execute(IvyArtifactRepository ivyArtifactRepository) { 34 | ivyArtifactRepository.setName('pygradle-pypi') 35 | ivyArtifactRepository.setUrl('https://linkedin.jfrog.io/linkedin/pypi-external/') 36 | ivyArtifactRepository.layout("pattern", new Action() { 37 | @Override 38 | void execute(IvyPatternRepositoryLayout repositoryLayout) { 39 | repositoryLayout.artifact('[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier]).[ext]') 40 | repositoryLayout.ivy('[organisation]/[module]/[revision]/[module]-[revision].ivy') 41 | repositoryLayout.setM2compatible(true) 42 | } 43 | }) 44 | } 45 | }) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/util/internal/TaskTimer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.util.internal; 17 | 18 | import java.util.HashMap; 19 | import java.util.Map; 20 | 21 | public class TaskTimer { 22 | 23 | private Map timingResults = new HashMap<>(); 24 | 25 | public TickingClock start(String name) { 26 | return new TickingClock(name, System.currentTimeMillis()); 27 | } 28 | 29 | public String buildReport() { 30 | StringBuilder sb = new StringBuilder(); 31 | timingResults.forEach((key, value) -> sb.append(key).append(":\t").append(value).append("\n")); 32 | 33 | return sb.toString(); 34 | } 35 | 36 | public class TickingClock { 37 | private final String name; 38 | private final long startedAt; 39 | 40 | TickingClock(String name, long startedAt) { 41 | this.name = name; 42 | this.startedAt = startedAt; 43 | } 44 | 45 | public void stop() { 46 | timingResults.put(name, System.currentTimeMillis() - startedAt); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/util/internal/zipapp/DefaultTemplateProviderOptions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.util.internal.zipapp; 17 | 18 | import com.linkedin.gradle.python.PythonExtension; 19 | import com.linkedin.gradle.python.util.zipapp.TemplateProviderOptions; 20 | import org.gradle.api.Project; 21 | 22 | /** 23 | * Provides some values to help pick which template to use 24 | */ 25 | public class DefaultTemplateProviderOptions implements TemplateProviderOptions { 26 | private final Project project; 27 | private final PythonExtension extension; 28 | private final String entryPoint; 29 | 30 | public DefaultTemplateProviderOptions(Project project, PythonExtension extension, String entryPoint) { 31 | this.project = project; 32 | this.extension = extension; 33 | this.entryPoint = entryPoint; 34 | } 35 | 36 | @Override 37 | public Project getProject() { 38 | return project; 39 | } 40 | 41 | @Override 42 | public PythonExtension getExtension() { 43 | return extension; 44 | } 45 | 46 | @Override 47 | public String getEntryPoint() { 48 | return entryPoint; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/util/internal/zipapp/ZipappGenerator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linkedin.gradle.python.util.internal.zipapp; 18 | 19 | import com.linkedin.gradle.python.PythonExtension; 20 | import java.util.Map; 21 | 22 | 23 | public interface ZipappGenerator { 24 | 25 | /** 26 | * When called, create the substitution map for the template generation. 27 | */ 28 | Map buildSubstitutions(PythonExtension extension, String entry); 29 | 30 | /** 31 | * When called will generate entry point files for a zipapp. 32 | * 33 | * @throws Exception when failing to build entry point 34 | */ 35 | void buildEntryPoints() throws Exception; 36 | } 37 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/util/pip/PipFreezeOutputParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.util.pip; 17 | 18 | import org.gradle.api.GradleException; 19 | 20 | import java.io.ByteArrayOutputStream; 21 | import java.util.Collection; 22 | import java.util.HashMap; 23 | import java.util.Map; 24 | 25 | public class PipFreezeOutputParser { 26 | 27 | private PipFreezeOutputParser() { 28 | //private constructor for utility class 29 | } 30 | 31 | static Map getDependencies(Collection ignoredDependencies, ByteArrayOutputStream requirements) { 32 | return getDependencies(ignoredDependencies, requirements.toString()); 33 | } 34 | 35 | static Map getDependencies(Collection ignoredDependencies, String requirements) { 36 | Map reqs = new HashMap<>(); 37 | 38 | // In regex world \n will also match the windows CR+LF 39 | for (String line : requirements.split("\n")) { 40 | if (!line.startsWith("-e ")) { // ignore editable requirements 41 | String[] parts = line.split("=="); 42 | if (parts.length != 2) { 43 | throw new GradleException("Unsupported requirement format. expected: ==. found: " + line); 44 | } 45 | String name = parts[0]; 46 | String version = parts[1]; 47 | /* 48 | * The tar name can have _ when package name has -, so check both. 49 | * The version will convert - into _ for wheel builds, so convert right here. 50 | */ 51 | if (!(ignoredDependencies.contains(name) || ignoredDependencies.contains(name.replace("-", "_")))) { 52 | reqs.put(name, version); 53 | } 54 | } 55 | } 56 | 57 | return reqs; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/util/zipapp/DefaultPexEntryPointTemplateProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.util.zipapp; 17 | 18 | import com.linkedin.gradle.python.PythonExtension; 19 | import com.linkedin.gradle.python.extension.CliExtension; 20 | import com.linkedin.gradle.python.util.ExtensionUtils; 21 | import org.apache.commons.io.IOUtils; 22 | 23 | import java.io.IOException; 24 | 25 | public class DefaultPexEntryPointTemplateProvider implements EntryPointTemplateProvider { 26 | 27 | @Override 28 | public String retrieveTemplate(TemplateProviderOptions options, boolean isPythonWrapper) throws IOException { 29 | PythonExtension extension = options.getExtension(); 30 | CliExtension cliExtension = ExtensionUtils.findPythonComponentExtension(extension, CliExtension.class); 31 | String template = (cliExtension != null && isPythonWrapper) 32 | ? "/templates/pex_cli_entrypoint.py.template" 33 | : "/templates/pex_non_cli_entrypoint.sh.template"; 34 | 35 | return IOUtils.toString(DefaultPexEntryPointTemplateProvider.class.getResourceAsStream(template)); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/util/zipapp/DefaultWebappEntryPointTemplateProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.util.zipapp; 17 | 18 | import com.linkedin.gradle.python.PythonExtension; 19 | import com.linkedin.gradle.python.extension.CliExtension; 20 | import com.linkedin.gradle.python.util.ExtensionUtils; 21 | import org.apache.commons.io.IOUtils; 22 | 23 | import java.io.IOException; 24 | 25 | public class DefaultWebappEntryPointTemplateProvider implements EntryPointTemplateProvider { 26 | 27 | @Override 28 | public String retrieveTemplate(TemplateProviderOptions options, boolean isPythonWrapper) throws IOException { 29 | PythonExtension extension = options.getExtension(); 30 | CliExtension cliExtension = ExtensionUtils.findPythonComponentExtension(extension, CliExtension.class); 31 | String template = (cliExtension != null && isPythonWrapper) 32 | ? "/templates/pex_cli_entrypoint.py.template" 33 | : "/templates/pex_non_cli_entrypoint.sh.template"; 34 | 35 | return IOUtils.toString(DefaultPexEntryPointTemplateProvider.class.getResourceAsStream(template)); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/util/zipapp/EntryPointTemplateProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.util.zipapp; 17 | 18 | import java.io.IOException; 19 | import java.io.Serializable; 20 | 21 | /** 22 | * Used to get a template to use for the entry points 23 | */ 24 | public interface EntryPointTemplateProvider extends Serializable { 25 | 26 | /** 27 | * Returns a template to be used for an entry point. The template must be renderable by 28 | * {@link groovy.text.SimpleTemplateEngine}. 29 | * 30 | * @param options to use to pick the template 31 | * @return A template to be used when building entry points. 32 | */ 33 | String retrieveTemplate(TemplateProviderOptions options, boolean isPythonWrapper) throws IOException; 34 | } 35 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/util/zipapp/TemplateProviderOptions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.util.zipapp; 17 | 18 | import com.linkedin.gradle.python.PythonExtension; 19 | import org.gradle.api.Project; 20 | 21 | public interface TemplateProviderOptions { 22 | Project getProject(); 23 | 24 | PythonExtension getExtension(); 25 | 26 | String getEntryPoint(); 27 | } 28 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/wheel/AbiDetails.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.wheel; 17 | 18 | import java.io.File; 19 | import java.io.Serializable; 20 | 21 | public class AbiDetails implements Serializable { 22 | 23 | private final File pythonExecutable; 24 | private final String pythonTag; 25 | private final String abiTag; 26 | private final String platformTag; 27 | 28 | public AbiDetails(File pythonExecutable, String pythonTag, String abiTag, String platformTag) { 29 | this.pythonExecutable = pythonExecutable; 30 | this.pythonTag = pythonTag; 31 | this.abiTag = abiTag; 32 | this.platformTag = platformTag; 33 | } 34 | 35 | public String getPythonTag() { 36 | return pythonTag; 37 | } 38 | 39 | public String getAbiTag() { 40 | return abiTag; 41 | } 42 | 43 | public String getPlatformTag() { 44 | return platformTag; 45 | } 46 | 47 | public File getPythonExecutable() { 48 | return pythonExecutable; 49 | } 50 | 51 | @Override 52 | public String toString() { 53 | return "AbiDetails{" 54 | + "pythonExec='" + pythonExecutable.toString() + '\'' 55 | + ", pythonTag='" + pythonTag + '\'' 56 | + ", abiTag='" + abiTag + '\'' 57 | + ", platformTag='" + platformTag + '\'' 58 | + '}'; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/wheel/EditablePythonAbiContainer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.wheel; 17 | 18 | /** 19 | * Adds an Python ABI to the container 20 | */ 21 | public interface EditablePythonAbiContainer extends PythonAbiContainer { 22 | 23 | /** 24 | * Adds an Python/ABI group to the container. 25 | */ 26 | void addSupportedAbi(AbiDetails triple); 27 | } 28 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/wheel/EmptyWheelCache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.wheel; 17 | 18 | import com.linkedin.gradle.python.extension.PythonDetails; 19 | 20 | import java.io.File; 21 | import java.util.Optional; 22 | 23 | public class EmptyWheelCache implements WheelCache { 24 | @Override 25 | public Optional findWheel(String name, String version, PythonDetails pythonDetails) { 26 | return Optional.empty(); 27 | } 28 | 29 | @Override 30 | public Optional findWheel(String name, String version, PythonDetails pythonDetails, WheelCacheLayer wheelCacheLayer) { 31 | return Optional.empty(); 32 | } 33 | 34 | @Override 35 | public void storeWheel(File wheel) { } 36 | 37 | @Override 38 | public void storeWheel(File wheel, WheelCacheLayer wheelCacheLayer) { } 39 | 40 | @Override 41 | public Optional getTargetDirectory() { 42 | return Optional.empty(); 43 | } 44 | 45 | @Override 46 | public boolean isWheelsReady() { 47 | return false; 48 | } 49 | 50 | @Override 51 | public void setWheelsReady(boolean wheelsReady) { } 52 | } 53 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/wheel/PythonAbiContainer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.wheel; 17 | 18 | import java.io.File; 19 | import java.io.Serializable; 20 | 21 | /** 22 | * Provides a mechanism for tooling to check if a python executable is compatible with a python 23 | * component. 24 | */ 25 | public interface PythonAbiContainer extends Serializable { 26 | /** 27 | * Checks to see if a python executable is compatible with a python version, python abi, and platform. 28 | * 29 | * For pythonTag, abiTag, and platformTag you can use a dot (.) between values to make multiple. For example 30 | * py2.py3 is python2 and python3. 31 | * 32 | * @param pythonExecutable to check against 33 | * @param pythonTag version of python (ex. py2.py3) 34 | * @param abiTag the ABI for the python exec 35 | * @param platformTag platform required 36 | * @return true is the python exec is compatible 37 | */ 38 | boolean matchesSupportedVersion(File pythonExecutable, String pythonTag, String abiTag, String platformTag); 39 | 40 | /** 41 | * Create and return a new object with the same containers 42 | */ 43 | PythonAbiContainer copy(); 44 | } 45 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/groovy/com/linkedin/gradle/python/wheel/WheelCacheLayer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.wheel; 17 | 18 | public enum WheelCacheLayer { 19 | PROJECT_LAYER("projectLayer"), 20 | HOST_LAYER("hostLayer"); 21 | 22 | private final String value; 23 | 24 | WheelCacheLayer(String val) { 25 | this.value = val; 26 | } 27 | 28 | @Override 29 | public String toString() { 30 | return value; 31 | } 32 | 33 | public String getValue() { 34 | return value; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/resources/templates/pex_cli_entrypoint.py.template: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """LinkedIn's command line pex invocation script.""" 4 | 5 | import os 6 | import sys 7 | 8 | 9 | def main(): 10 | tool_dir = os.path.dirname(os.path.realpath(__file__)) 11 | pex_exec = os.path.join(tool_dir, '$realPex') 12 | os.environ.update( 13 | dict(PEX_MODULE='$entryPoint') 14 | ) 15 | os.execve(pex_exec, sys.argv, os.environ) 16 | 17 | 18 | if __name__ == '__main__': 19 | main() 20 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/resources/templates/pex_non_cli_entrypoint.sh.template: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | [ -z "\$BASEDIR" ] && BASEDIR=\$( cd "\$( dirname "\${BASH_SOURCE[0]}" )/.." && pwd ) 3 | exec /usr/bin/env PEX_ROOT="\$BASEDIR/libexec" PEX_MODULE="${entryPoint}" \$BASEDIR/bin/${realPex} "\$@" 4 | -------------------------------------------------------------------------------- /pygradle-plugin/src/main/resources/templates/wheel-api.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2016 LinkedIn Corp. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from wheel.pep425tags import get_supported 18 | from json import dump 19 | import sys 20 | 21 | supported_values = [] 22 | for entry in get_supported(): 23 | supported_values.append( { 'pythonTag': entry[0], 'abiTag': entry[1], 'platformTag': entry[2] }) 24 | 25 | result_file = sys.argv[1] 26 | with open(result_file, 'w') as outfile: 27 | dump(supported_values, outfile) 28 | 29 | -------------------------------------------------------------------------------- /pygradle-plugin/src/test/golang/README.md: -------------------------------------------------------------------------------- 1 | # How to build the stub applications 2 | 3 | ``` 4 | GOOS=windows GOARCH=386 go build -o python26.exe python26.go 5 | GOOS=windows GOARCH=386 go build -o python27.exe python27.go 6 | GOOS=windows GOARCH=386 go build -o python31.exe python31.go 7 | ``` 8 | -------------------------------------------------------------------------------- /pygradle-plugin/src/test/golang/python26.go: -------------------------------------------------------------------------------- 1 | package main 2 | import "fmt" 3 | func main() { 4 | fmt.Println("Python 2.6") 5 | } 6 | -------------------------------------------------------------------------------- /pygradle-plugin/src/test/golang/python27.go: -------------------------------------------------------------------------------- 1 | package main 2 | import "fmt" 3 | func main() { 4 | fmt.Println("Python 2.7") 5 | } 6 | -------------------------------------------------------------------------------- /pygradle-plugin/src/test/golang/python35.go: -------------------------------------------------------------------------------- 1 | package main 2 | import "fmt" 3 | func main() { 4 | fmt.Println("Python 3.5") 5 | } 6 | -------------------------------------------------------------------------------- /pygradle-plugin/src/test/groovy/com/linkedin/gradle/python/checkstyle/StyleViolationTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.checkstyle 17 | 18 | 19 | import com.linkedin.gradle.python.checkstyle.model.StyleViolation 20 | import spock.lang.Specification 21 | import spock.lang.Unroll 22 | 23 | class StyleViolationTest extends Specification { 24 | 25 | @Unroll 26 | def 'can parse #code code correctly'() { 27 | expect: 28 | new StyleViolation(null, null, code, null).violationType == type 29 | 30 | where: 31 | code | type 32 | 'E101' | StyleViolation.ViolationType.ERROR 33 | 'W102' | StyleViolation.ViolationType.WARNING 34 | 'F103' | StyleViolation.ViolationType.PY_FLAKES 35 | 'C104' | StyleViolation.ViolationType.COMPLEXITY 36 | 'N103' | StyleViolation.ViolationType.NAMING 37 | 'Q000' | StyleViolation.ViolationType.OTHER 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /pygradle-plugin/src/test/groovy/com/linkedin/gradle/python/checkstyle/model/TestStyleViolation.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.checkstyle.model 17 | 18 | 19 | import spock.lang.Specification 20 | import spock.lang.Unroll 21 | 22 | class TestStyleViolation extends Specification { 23 | 24 | @Unroll 25 | def 'can parse #code code correctly'() { 26 | expect: 27 | new StyleViolation(null, null, code, null).violationType == type 28 | 29 | where: 30 | code | type 31 | 'E101' | StyleViolation.ViolationType.ERROR 32 | 'W102' | StyleViolation.ViolationType.WARNING 33 | 'F103' | StyleViolation.ViolationType.PY_FLAKES 34 | 'C104' | StyleViolation.ViolationType.COMPLEXITY 35 | 'N103' | StyleViolation.ViolationType.NAMING 36 | 'Q000' | StyleViolation.ViolationType.OTHER 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /pygradle-plugin/src/test/groovy/com/linkedin/gradle/python/extension/DefaultPythonDetailsTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.extension 17 | 18 | import com.linkedin.gradle.python.extension.internal.DefaultPythonDetails 19 | import org.gradle.api.GradleException 20 | import org.gradle.testfixtures.ProjectBuilder 21 | import spock.lang.Specification 22 | 23 | 24 | class DefaultPythonDetailsTest extends Specification { 25 | 26 | def project = new ProjectBuilder().build() 27 | def details = new DefaultPythonDetails(project, new File("/foo/bar/venv")) 28 | 29 | /* Most of the work is done by the PythonDefaultVersions class, so see 30 | that class and tests. We'd like to be able to completely test 31 | PythonDetails.setPythonVersion() too, but that's currently impossible, 32 | since that method searches for a Python interpreter on the file system 33 | matching the selected version. We can't guarantee that such a Python 34 | version will exist so we can't test it without perhaps some mocking of 35 | operatingSystem.findInPath() , which I haven't been able to come up 36 | with yet. 37 | 38 | It does seem like this one can be tested though. 39 | */ 40 | def 'test set to unacceptable version'() { 41 | when: 42 | details.setPythonDefaultVersions(new PythonDefaultVersions(['2.7', '3.5', '3.6'])) 43 | details.setPythonVersion('2.5') 44 | 45 | then: 46 | thrown(GradleException) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /pygradle-plugin/src/test/groovy/com/linkedin/gradle/python/extension/PythonDetailsTestDouble.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.extension; 17 | 18 | import com.linkedin.gradle.python.extension.internal.DefaultPythonDetails; 19 | import org.gradle.api.Project; 20 | 21 | import java.io.File; 22 | 23 | public class PythonDetailsTestDouble extends DefaultPythonDetails { 24 | 25 | private PythonVersion version = new PythonVersion("3.6"); 26 | 27 | public PythonDetailsTestDouble(Project project, File venvDir) { 28 | super(project, venvDir); 29 | } 30 | 31 | @Override 32 | public PythonVersion getPythonVersion() { 33 | return version; 34 | } 35 | 36 | public void setPythonVersion(PythonVersion pythonVersion) { 37 | this.version = pythonVersion; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /pygradle-plugin/src/test/groovy/com/linkedin/gradle/python/extension/PythonVersionTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.extension 17 | 18 | import spock.lang.Specification 19 | import spock.lang.Unroll 20 | 21 | 22 | class PythonVersionTest extends Specification { 23 | 24 | @Unroll 25 | def 'get #a Python version'() { 26 | expect: 27 | new PythonVersion(a).getPythonVersion() == a 28 | 29 | where: 30 | a || _ 31 | '2.7.11' || _ 32 | '3.5.2' || _ 33 | } 34 | 35 | @Unroll 36 | def 'test #a major minor #b'() { 37 | expect: 38 | new PythonVersion(a).getPythonMajorMinor() == b 39 | 40 | where: 41 | a || b 42 | '2.7.11' || '2.7' 43 | '3.5.2' || '3.5' 44 | } 45 | 46 | @Unroll 47 | def 'test #a major #b'() { 48 | expect: 49 | new PythonVersion(a).getPythonMajor() == b 50 | 51 | where: 52 | a || b 53 | '2.7.11' || '2' 54 | '3.5.2' || '3' 55 | } 56 | 57 | @Unroll 58 | def 'test #a minor #b'() { 59 | expect: 60 | new PythonVersion(a).getPythonMinor() == b 61 | 62 | where: 63 | a || b 64 | '2.7.11' || '7' 65 | '3.5.2' || '5' 66 | } 67 | 68 | @Unroll 69 | def 'test #a patch #b'() { 70 | expect: 71 | new PythonVersion(a).getPythonPatch() == b 72 | 73 | where: 74 | a || b 75 | '2.7.11' || '11' 76 | '3.5.2' || '2' 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /pygradle-plugin/src/test/groovy/com/linkedin/gradle/python/plugin/PythonCliDistributionPluginTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.plugin 17 | 18 | 19 | import org.gradle.testfixtures.ProjectBuilder 20 | import spock.lang.Specification 21 | 22 | class PythonCliDistributionPluginTest extends Specification { 23 | 24 | def 'can apply python cli plugin resource'() { 25 | when: 26 | def project = new ProjectBuilder().build() 27 | then: 28 | project.plugins.apply('com.linkedin.python-cli') 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /pygradle-plugin/src/test/groovy/com/linkedin/gradle/python/plugin/PythonPexDistributionPluginTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.plugin 17 | 18 | 19 | import org.gradle.testfixtures.ProjectBuilder 20 | import spock.lang.Specification 21 | 22 | 23 | class PythonPexDistributionPluginTest extends Specification { 24 | 25 | def 'can apply python pex plugin resource'() { 26 | when: 27 | def project = new ProjectBuilder().build() 28 | then: 29 | project.plugins.apply('com.linkedin.python-pex') 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /pygradle-plugin/src/test/groovy/com/linkedin/gradle/python/plugin/PythonSourceDistributionPluginTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.plugin 17 | 18 | 19 | import org.gradle.testfixtures.ProjectBuilder 20 | import spock.lang.Specification 21 | 22 | class PythonSourceDistributionPluginTest extends Specification { 23 | 24 | def "can apply python plugin class"() { 25 | when: 26 | def project = new ProjectBuilder().build() 27 | then: 28 | project.plugins.apply('com.linkedin.python-sdist') 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /pygradle-plugin/src/test/groovy/com/linkedin/gradle/python/tasks/exec/ExternalExecFailTestDouble.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.tasks.exec 17 | 18 | import org.gradle.api.Action 19 | import org.gradle.process.ExecResult 20 | import org.gradle.process.ExecSpec 21 | 22 | 23 | class ExternalExecFailTestDouble implements ExternalExec { 24 | 25 | private final ExecSpec spec 26 | 27 | ExternalExecFailTestDouble(ExecSpec spec) { 28 | this.spec = spec 29 | } 30 | 31 | @Override 32 | ExecResult exec(Action action) { 33 | action.execute(spec) 34 | return ['getExitValue': { -> 1 }] as ExecResult 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /pygradle-plugin/src/test/groovy/com/linkedin/gradle/python/tasks/exec/ExternalExecTestDouble.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.tasks.exec 17 | 18 | import org.gradle.api.Action 19 | import org.gradle.process.ExecResult 20 | import org.gradle.process.ExecSpec 21 | 22 | class ExternalExecTestDouble implements ExternalExec { 23 | 24 | private final ExecSpec spec 25 | 26 | ExternalExecTestDouble(ExecSpec spec) { 27 | this.spec = spec 28 | } 29 | 30 | @Override 31 | ExecResult exec(Action action) { 32 | action.execute(spec) 33 | return ['getExitValue': { -> 0 }] as ExecResult 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /pygradle-plugin/src/test/groovy/com/linkedin/gradle/python/util/EnvironmentMergerTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.util 17 | 18 | import spock.lang.Specification 19 | 20 | 21 | /** 22 | * Unit tests for environment merger implementers. 23 | */ 24 | class EnvironmentMergerTest extends Specification { 25 | EnvironmentMerger merger = new DefaultEnvironmentMerger() 26 | 27 | def "merge a list of environments"() { 28 | when: "we use a default merger to merge a list of environments" 29 | Map mergedEnv = merger.mergeEnvironments([['A': '1'], ['B': '2'], ['C': '3']]) 30 | 31 | then: "we get merged environment back" 32 | mergedEnv == ['A': '1', 'B': '2', 'C': '3'] 33 | } 34 | 35 | def "merge into a master environment"() { 36 | when: "we use a default merger to merge a list of environments into a master environment" 37 | Map masterEnv = ['X': '10', 'Y': '20', 'A': '5'] 38 | Map mergedEnv = merger.mergeEnvironments([masterEnv, ['A': '1'], ['A': '2'], ['A': '3']]) 39 | 40 | then: "we get merged environments with keys overriding in order of appearance with last winning" 41 | mergedEnv == ['A': '3', 'X': '10', 'Y': '20'] 42 | } 43 | 44 | def "merge one environment into another"() { 45 | when: "we use a default merger to merge a source environment into a target environment" 46 | Map target = ['A': '1', 'B': '2'] 47 | merger.mergeIntoEnvironment(target, ['A': '10', 'C': '3']) 48 | 49 | then: "we get merged environments with keys overriding target from source" 50 | target == ['A': '10', 'B': '2', 'C': '3'] 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /pygradle-plugin/src/test/groovy/com/linkedin/gradle/python/util/WindowsBinaryUnpacker.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.util 17 | 18 | import com.linkedin.gradle.python.extension.DefaultPythonDetailsWindowsTest 19 | 20 | class WindowsBinaryUnpacker { 21 | private WindowsBinaryUnpacker() { } 22 | 23 | enum PythonVersion { 24 | PYTHON_27(2, 7), 25 | PYTHON_35(3, 5), 26 | PYTHON_36(3, 6), 27 | PYTHON_37(3, 7), 28 | 29 | final int major 30 | final int minor 31 | 32 | PythonVersion(int major, int minor) { 33 | this.major = major 34 | this.minor = minor 35 | } 36 | } 37 | 38 | public static File buildPythonExec(File destination, PythonVersion version) { 39 | def exeRoot = DefaultPythonDetailsWindowsTest.getResource("/windows/python/shim/python${version.major}${version.minor}.exe") 40 | 41 | new File(destination, "python${version.major}.${version.minor}.exe").withOutputStream { out -> 42 | out << exeRoot.openStream() 43 | } 44 | 45 | new File(destination, "python${version.major}.exe").withOutputStream { out -> 46 | out << exeRoot.openStream() 47 | } 48 | 49 | new File(destination, "python.exe").withOutputStream { out -> 50 | out << exeRoot.openStream() 51 | } 52 | 53 | return destination 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /pygradle-plugin/src/test/groovy/com/linkedin/gradle/python/util/internal/pex/PexExecOutputParserTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.util.internal.pex 17 | 18 | import org.gradle.api.GradleException 19 | import spock.lang.Specification 20 | 21 | 22 | class PexExecOutputParserTest extends Specification { 23 | 24 | def 'can parse output from pex'() { 25 | given: 26 | def output = """\ 27 | |Processing /export/home/tester/hudson/data/workspace/MP_TRUNKDEV_MP_DEP/ratchet_895fe7e12c1d15e1204d9fa9c0c05534176c2f12/ratchet 28 | |Building wheels for collected packages: ratchet 29 | | Running setup.py bdist_wheel for ratchet 30 | | Stored in directory: /export/home/tester/hudson/data/workspace/MP_TRUNKDEV_MP_DEP/ratchet_895fe7e12c1d15e1204d9fa9c0c05534176c2f12/build/ratchet/wheel-cache 31 | |Successfully built ratchet 32 | |Could not satisfy all requirements for pyasn1: 33 | | pyasn1, pyasn1==0.1.7(from: oauth2client) 34 | | 35 | |NOTE: In order to use the FileCache you must have 36 | |lockfile installed. You can install it via pip: 37 | | pip install lockfile""".stripMargin().stripIndent() 38 | 39 | when: 40 | def parser = new PexExecOutputParser(output, 1) 41 | parser.validatePexBuildSuccessfully() 42 | 43 | then: 44 | def e = thrown(GradleException) 45 | e.message.startsWith("Failed to build a pex file (see output above)!") 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /pygradle-plugin/src/test/groovy/com/linkedin/gradle/python/wheel/PythonAbiContainerTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.wheel 17 | 18 | import com.linkedin.gradle.python.wheel.internal.DefaultPythonAbiContainer 19 | import org.junit.Rule 20 | import org.junit.rules.TemporaryFolder 21 | import spock.lang.Specification 22 | 23 | class PythonAbiContainerTest extends Specification { 24 | 25 | @Rule 26 | TemporaryFolder temporaryFolder 27 | 28 | def 'will explode the compact keys'() { 29 | def pythonExe = temporaryFolder.newFile('python') 30 | def formats = new DefaultPythonAbiContainer() 31 | formats.addSupportedAbi(new AbiDetails(pythonExe, 'py2', 'none', 'any')) 32 | formats.addSupportedAbi(new AbiDetails(pythonExe, 'py3', 'cp27mu', 'any')) 33 | 34 | expect: 35 | formats.matchesSupportedVersion(pythonExe, 'py2.py3', 'none', 'any') 36 | formats.matchesSupportedVersion(pythonExe, 'py2.py3', 'cp27mu', 'any') 37 | !formats.matchesSupportedVersion(pythonExe, 'py3', 'none', 'any') 38 | } 39 | 40 | def 'different pythons will not match'() { 41 | def python1Exe = temporaryFolder.newFile('python1') 42 | def python2Exe = temporaryFolder.newFile('python2') 43 | def formats = new DefaultPythonAbiContainer() 44 | formats.addSupportedAbi(new AbiDetails(python1Exe, 'py2', 'none', 'any')) 45 | formats.addSupportedAbi(new AbiDetails(python2Exe, 'py3', 'cp27mu', 'any')) 46 | 47 | expect: 48 | formats.matchesSupportedVersion(python1Exe, 'py2.py3', 'none', 'any') 49 | !formats.matchesSupportedVersion(python2Exe, 'py2.py3', 'none', 'any') 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /pygradle-plugin/src/test/groovy/com/linkedin/gradle/python/wheel/PythonWheelDetailsTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 LinkedIn Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.linkedin.gradle.python.wheel 17 | 18 | import spock.lang.Specification 19 | 20 | class PythonWheelDetailsTest extends Specification { 21 | 22 | def 'can parse wheel file'() { 23 | when: 24 | def wheelFile = new File("foo-1.0.0-py2-none-any.whl") 25 | PythonWheelDetails pythonWheelDetails = PythonWheelDetails.fromFile(wheelFile).get() 26 | then: 27 | pythonWheelDetails.dist == "foo" 28 | pythonWheelDetails.version == "1.0.0" 29 | pythonWheelDetails.pythonTag == "py2" 30 | pythonWheelDetails.abiTag == "none" 31 | pythonWheelDetails.platformTag == "any" 32 | } 33 | 34 | def 'can parse wheel file with SNAPSHOT version'() { 35 | when: 36 | def wheelFile = new File("foo-1.0.0_SNAPSHOT-py2-none-any.whl") 37 | PythonWheelDetails pythonWheelDetails = PythonWheelDetails.fromFile(wheelFile).get() 38 | then: 39 | pythonWheelDetails.dist == "foo" 40 | pythonWheelDetails.version == "1.0.0-SNAPSHOT" 41 | pythonWheelDetails.pythonTag == "py2" 42 | pythonWheelDetails.abiTag == "none" 43 | pythonWheelDetails.platformTag == "any" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /pygradle-plugin/src/test/resources/checkstyle/checkstyle.xsd: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /pygradle-plugin/src/test/resources/windows/python/shim/python26.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linkedin/pygradle/3e724f26d9dece4746623a7e94028fef438e7cbc/pygradle-plugin/src/test/resources/windows/python/shim/python26.exe -------------------------------------------------------------------------------- /pygradle-plugin/src/test/resources/windows/python/shim/python27.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linkedin/pygradle/3e724f26d9dece4746623a7e94028fef438e7cbc/pygradle-plugin/src/test/resources/windows/python/shim/python27.exe -------------------------------------------------------------------------------- /pygradle-plugin/src/test/resources/windows/python/shim/python35.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linkedin/pygradle/3e724f26d9dece4746623a7e94028fef438e7cbc/pygradle-plugin/src/test/resources/windows/python/shim/python35.exe -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':pygradle-plugin' 2 | include ':pivy-importer' 3 | -------------------------------------------------------------------------------- /version.properties: -------------------------------------------------------------------------------- 1 | version=0.12.11 2 | --------------------------------------------------------------------------------