Skip to content

Commit

Permalink
Merge pull request #298 from warsaw/unrevert
Browse files Browse the repository at this point in the history
Revert "Reverting Zipapp Container refactoring."
  • Loading branch information
warsaw authored Apr 19, 2019
2 parents 075a529 + 1632371 commit 71994ab
Show file tree
Hide file tree
Showing 22 changed files with 478 additions and 103 deletions.
10 changes: 5 additions & 5 deletions docs/developers.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ test and add `mavenLocal()` to the repositories. This will configure your
project to look in `~/.m2` in addition to other repositories you have
configured when pulling artifacts.

If you are building on Windows, PyGradle will avoid using your system temporary
folder for integration tests and instead create and use the folder `c:\tmp`. This
is to avoid issues with Windows's 260 character path limit. Make sure your user
If you are building on Windows, PyGradle will avoid using your system temporary
folder for integration tests and instead create and use the folder `c:\tmp`. This
is to avoid issues with Windows's 260 character path limit. Make sure your user
account has the ablity to create and use this folder or your integration tests will fail.

If you are using Windows 10, it is possible to go beyond that 260 character limit,
but not with this product. That policy change only applies to managed applications.
If you are using Windows 10, it is possible to go beyond that 260 character limit,
but not with this product. That policy change only applies to managed applications.
PyGradle's unit tests are not managed applications.


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ class ParallelWheelsIntegrationTest extends Specification {
result.task(':foo:pytest').outcome == TaskOutcome.SUCCESS
result.task(':foo:check').outcome == TaskOutcome.SUCCESS
result.task(':foo:build').outcome == TaskOutcome.SUCCESS
result.task(':foo:buildPex').outcome == TaskOutcome.SUCCESS
result.task(':foo:assembleContainers').outcome == TaskOutcome.SUCCESS

deployablePath.resolve('hello_world').toFile().exists()
deployablePath.resolve(PexFileUtil.createThinPexFilename('foo')).toFile().exists()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ class PexIntegrationTest extends Specification {
result.task(':foo:check').outcome == TaskOutcome.SUCCESS
result.task(':foo:build').outcome == TaskOutcome.SUCCESS
result.task(':foo:buildPex').outcome == TaskOutcome.SUCCESS
result.task(':foo:assembleContainers').outcome == TaskOutcome.SUCCESS

deployablePath.resolve('hello_world').toFile().exists()
deployablePath.resolve(PexFileUtil.createThinPexFilename('foo')).toFile().exists()
Expand Down Expand Up @@ -129,6 +130,7 @@ class PexIntegrationTest extends Specification {
result.task(':foo:check').outcome == TaskOutcome.SUCCESS
result.task(':foo:build').outcome == TaskOutcome.SUCCESS
result.task(':foo:buildPex').outcome == TaskOutcome.SUCCESS
result.task(':foo:assembleContainers').outcome == TaskOutcome.SUCCESS

Path deployablePath = testProjectDir.root.toPath().resolve(Paths.get('foo', 'build', 'deployable', "bin"))
def pexFile = deployablePath.resolve(PexFileUtil.createFatPexFilename('hello_world'))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
*/
package com.linkedin.gradle.python

import com.linkedin.gradle.python.extension.PexExtension
import com.linkedin.gradle.python.extension.PythonDetails
import com.linkedin.gradle.python.extension.PythonDetailsFactory
import com.linkedin.gradle.python.util.ApplicationContainer
import org.gradle.api.GradleException
import org.gradle.api.Project

Expand Down Expand Up @@ -80,6 +82,28 @@ class PythonExtension {
/* Container of the details related to the venv/python instance */
private final PythonDetails details

/*
* "Application container" defines the format for bundling the application
* into a single file distribution. Examples include pex, shiv, and xar.
* Not all plugins using this extension support such containers, but for
* u/i purposes, it's convenient to add this here. This allows the
* following in a build.gradle file:
*
* python {
* container = "shiv"
* }
*
* These will simply be ignored in extension clients that don't need it.
*
* Downstream consumers can extend the map between container short names
* appropriate for the build.gradle UI, and the container class
* this maps to. They can also set the default container, which allows
* them e.g. to choose shivs over pexes.
*/
public Map<String, ApplicationContainer> containers
String container
ApplicationContainer defaultContainer

PythonExtension(Project project) {
this.details = PythonDetailsFactory.makePythonDetails(project, null)
docsDir = Paths.get(project.projectDir.absolutePath, "docs").toFile().path
Expand All @@ -92,10 +116,18 @@ class PythonExtension {
def applicationDirectory = PythonDetailsFactory.getPythonApplicationDirectory()

pythonEnvironment = [
'PATH': "${ -> details.virtualEnv.toPath().resolve(applicationDirectory).toAbsolutePath().toString() }" + File.pathSeparator + System.getenv('PATH'),]
'PATH': "${ -> details.virtualEnv.toPath().resolve(applicationDirectory).toAbsolutePath().toString() }"
+ File.pathSeparator
+ System.getenv('PATH'),
]

pythonEnvironmentDistgradle = ['PYGRADLE_PROJECT_NAME' : project.name,
'PYGRADLE_PROJECT_VERSION': "${ -> project.version }",]
pythonEnvironmentDistgradle = [
'PYGRADLE_PROJECT_NAME' : project.name,
'PYGRADLE_PROJECT_VERSION': "${ -> project.version }",
]

defaultContainer = new PexExtension(project)
containers = [pex: defaultContainer]

/*
* NOTE: Do lots of sanity checking and validation here.
Expand Down Expand Up @@ -155,4 +187,15 @@ class PythonExtension {
void setPinnedFile(File pinnedFile) {
this.pinnedFile = pinnedFile
}

/*
* Use this as the programmatic API for getting the current container extension.
*/
ApplicationContainer getApplicationContainer() {
// Why am I doing it this way? Because if the container isn't set
// (i.e. it is null) I want to return the default container. But if
// it's set to a bogus value, I want it to return the null so the
// caller will know they have a bogus value.
return container == null ? defaultContainer : containers.get(container)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,29 @@
*/
package com.linkedin.gradle.python.extension;

import com.linkedin.gradle.python.PythonExtension;
import com.linkedin.gradle.python.tasks.BuildPexTask;
import com.linkedin.gradle.python.util.ApplicationContainer;
import com.linkedin.gradle.python.util.ExtensionUtils;
import com.linkedin.gradle.python.util.OperatingSystem;
import com.linkedin.gradle.python.util.StandardTextValues;
import org.gradle.api.Project;

import java.io.File;


public class PexExtension implements ZipappExtension {
public class PexExtension implements ApplicationContainer {
// 2019-04-01(warsaw): For backward compatibility, we must expose a no-op
// buildPex task unconditionally. This will be created in
// PythonContainerPlugin and tied into the task hierarchy in the right
// place. realBuildPex task is the actual pex building task, but these
// are only needed if pexes are selected (and won't get created until
// after build.gradle evaluation).
//
// Yes, this is gross and we should deprecate this mess at our earliest convenience.
public static final String TASK_BUILD_PEX = "realBuildPex";
public static final String TASK_BUILD_NOOP_PEX = "buildPex";

private File cache;
// Default to fat zipapps on Windows, since our wrappers are fairly POSIX specific.
private boolean isFat = OperatingSystem.current().isWindows();
Expand Down Expand Up @@ -93,4 +109,19 @@ public File getCache() {
public void setCache(File cache) {
this.cache = cache;
}

public void addExtensions(Project project) {
ExtensionUtils.maybeCreatePexExtension(project);
}

public void addDependencies(Project project) {
final PythonExtension extension = ExtensionUtils.getPythonExtension(project);

project.getDependencies().add(StandardTextValues.CONFIGURATION_BUILD_REQS.getValue(),
extension.forcedVersions.get("pex"));
}

public void makeTasks(Project project) {
project.getTasks().create(TASK_BUILD_PEX, BuildPexTask.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,29 @@
*/
package com.linkedin.gradle.python.plugin;

import com.linkedin.gradle.python.util.ApplicationContainer;
import com.linkedin.gradle.python.tasks.GenerateCompletionsTask;
import com.linkedin.gradle.python.util.ExtensionUtils;
import com.linkedin.gradle.python.util.StandardTextValues;
import org.gradle.api.Project;
import org.gradle.api.tasks.TaskContainer;


public class PythonCliDistributionPlugin extends PythonBasePlugin {
public class PythonCliDistributionPlugin extends PythonContainerPlugin {

public static final String TASK_GENERATE_COMPLETIONS = "generateCompletions";

@Override
public void applyTo(Project project) {
project.getPlugins().apply(PythonPexDistributionPlugin.class);
project.getPlugins().apply(PythonContainerPlugin.class);
ExtensionUtils.maybeCreateCliExtension(project);

GenerateCompletionsTask completionsTask = project.getTasks().create(TASK_GENERATE_COMPLETIONS, GenerateCompletionsTask.class);
completionsTask.dependsOn(project.getTasks().getByName(StandardTextValues.TASK_INSTALL_PROJECT.getValue()));
TaskContainer tasks = project.getTasks();

project.getTasks().getByName(PythonPexDistributionPlugin.TASK_BUILD_PEX).dependsOn(project.getTasks().getByName(TASK_GENERATE_COMPLETIONS));
}
GenerateCompletionsTask completionsTask = tasks.create(TASK_GENERATE_COMPLETIONS, GenerateCompletionsTask.class);
completionsTask.dependsOn(tasks.getByName(StandardTextValues.TASK_INSTALL_PROJECT.getValue()));

tasks.getByName(ApplicationContainer.TASK_ASSEMBLE_CONTAINERS)
.dependsOn(project.getTasks().getByName(TASK_GENERATE_COMPLETIONS));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* Copyright 2016 LinkedIn Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.linkedin.gradle.python.plugin;

import com.linkedin.gradle.python.PythonExtension;
import com.linkedin.gradle.python.extension.DeployableExtension;
import com.linkedin.gradle.python.extension.PexExtension;
import com.linkedin.gradle.python.tasks.BuildWheelsTask;
import com.linkedin.gradle.python.tasks.NoopBuildPexTask;
import com.linkedin.gradle.python.tasks.PythonContainerTask;
import com.linkedin.gradle.python.util.ApplicationContainer;
import com.linkedin.gradle.python.util.ExtensionUtils;
import com.linkedin.gradle.python.util.StandardTextValues;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.tasks.TaskContainer;
import org.gradle.api.tasks.bundling.Compression;
import org.gradle.api.tasks.bundling.Tar;


public class PythonContainerPlugin extends PythonBasePlugin {
@Override
public void applyTo(final Project project) {

project.getPlugins().apply(PythonPlugin.class);
final PythonExtension pythonExtension = ExtensionUtils.getPythonExtension(project);
ExtensionUtils.maybeCreateWheelExtension(project);

final DeployableExtension deployableExtension = ExtensionUtils.maybeCreateDeployableExtension(project);
final ApplicationContainer applicationContainer = pythonExtension.getApplicationContainer();

applicationContainer.addExtensions(project);

/*
* Build wheels, first of dependencies, then of the current project.
* However, it's possible that we will have multiple containers
* (e.g. pex and shiv), so be sure to only build the wheels once.
*
* TODO 2019-03-19: Adapt this to on-host layered caching.
*/
TaskContainer tasks = project.getTasks();


// Add this no-op task for backward compatibility. See PexExtension for details.
Task noop = tasks.findByName(PexExtension.TASK_BUILD_NOOP_PEX);
if (noop == null) {
noop = tasks.create(PexExtension.TASK_BUILD_NOOP_PEX, NoopBuildPexTask.class);
}

if (tasks.findByName(ApplicationContainer.TASK_ASSEMBLE_CONTAINERS) == null) {
BuildWheelsTask buildWheelsTask = tasks.create(ApplicationContainer.TASK_BUILD_WHEELS, BuildWheelsTask.class);
buildWheelsTask.setInstallFileCollection(project.getConfigurations().getByName("python"));
buildWheelsTask.dependsOn(tasks.getByName(StandardTextValues.TASK_INSTALL_PROJECT.getValue()));

BuildWheelsTask projectWheelsTask = tasks.create(ApplicationContainer.TASK_BUILD_PROJECT_WHEEL, BuildWheelsTask.class);
projectWheelsTask.setInstallFileCollection(project.files(project.file(project.getProjectDir())));
projectWheelsTask.setEnvironment(pythonExtension.pythonEnvironmentDistgradle);
projectWheelsTask.dependsOn(tasks.getByName(ApplicationContainer.TASK_BUILD_WHEELS));

/*
* This is just a lifecycle task which provides a convenient place
* to add specific container dependencies on, without those
* extensions having to know too many intimate details about
* generic Python builds. E.g. we make the pex task depend on it.
*/
Task assemble = tasks.create(ApplicationContainer.TASK_ASSEMBLE_CONTAINERS);
assemble.dependsOn(noop);

Tar tar = tasks.create(ApplicationContainer.TASK_PACKAGE_DEPLOYABLE, Tar.class);
tar.setCompression(Compression.GZIP);
tar.setBaseName(project.getName());
tar.setExtension("tar.gz");
tar.from(deployableExtension.getDeployableBuildDir());
tar.dependsOn(assemble);
project.getArtifacts().add(StandardTextValues.CONFIGURATION_DEFAULT.getValue(), tar);
}

// This must happen after build.gradle file evaluation.
project.afterEvaluate(it -> {
// The application container might have changed.
final ApplicationContainer postContainer = pythonExtension.getApplicationContainer();

if (postContainer == null) {
throw new IllegalArgumentException(
"Unknown Python application container: "
+ pythonExtension.getContainer());
}

/*
* Plumb the container tasks into the task hierarchy. The
* assemble task depends on all the implementers of
* PythonContainerTask, and the deployable task depends on the
* assemble task.
*
* While we're doing this though, suppress the deprecation warning
* normally thrown in NoopBuildPexTask when user code calls its
* .dependsOn().
*/
postContainer.addDependencies(project);
postContainer.makeTasks(project);

NoopBuildPexTask noopTask = (NoopBuildPexTask) tasks.findByName(PexExtension.TASK_BUILD_NOOP_PEX);
noopTask.suppressWarning = true;

Task assemble = tasks.getByName(ApplicationContainer.TASK_ASSEMBLE_CONTAINERS);
Task parent = tasks.getByName(ApplicationContainer.TASK_BUILD_PROJECT_WHEEL);

for (Task task : tasks.withType(PythonContainerTask.class)) {
assemble.dependsOn(task);
task.dependsOn(parent);
}

// Turn the warning back on.
noopTask.suppressWarning = false;
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.linkedin.gradle.python.plugin;

import com.linkedin.gradle.python.util.ApplicationContainer;
import com.linkedin.gradle.python.extension.DeployableExtension;
import com.linkedin.gradle.python.util.ExtensionUtils;
import com.linkedin.gradle.python.util.FileSystemUtils;
Expand Down Expand Up @@ -102,8 +103,9 @@ public void apply(final Project project) {
copy.from(resourceConf);
copy.into(deployableExtension.getDeployableBuildDir().toPath().resolve("resource"));
});

// Make sure we've copied all the files before running the task: packageDeployable
project.getTasks().getByName(PythonPexDistributionPlugin.TASK_PACKAGE_DEPLOYABLE)
project.getTasks().getByName(ApplicationContainer.TASK_PACKAGE_DEPLOYABLE)
.dependsOn(project.getTasks().getByName(TASK_PACKAGE_RESOURCE_FILES));
}
}
Loading

0 comments on commit 71994ab

Please sign in to comment.