mirror of https://github.com/apache/maven.git
Forgot to remove the deleted files.
This commit is contained in:
parent
09961c3150
commit
ccc7cb3abc
|
@ -1,220 +0,0 @@
|
|||
package org.apache.maven.lifecycle.internal;
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import org.apache.maven.project.MavenProject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
* @author Kristian Rosenvold
|
||||
* NOTE: This class is not part of any public api and can be changed or deleted without prior notice.
|
||||
*/
|
||||
public class BuildLogItem
|
||||
{
|
||||
private final ExecutionPlanItem executionPlanItem;
|
||||
|
||||
private final MavenProject project;
|
||||
|
||||
private final long startTime;
|
||||
|
||||
private long endTime;
|
||||
|
||||
private final List<DependencyLogEntry> dependencies =
|
||||
Collections.synchronizedList( new ArrayList<DependencyLogEntry>() );
|
||||
|
||||
public BuildLogItem( MavenProject project, ExecutionPlanItem executionPlanItem )
|
||||
{
|
||||
this.executionPlanItem = executionPlanItem;
|
||||
this.project = project;
|
||||
startTime = System.currentTimeMillis();
|
||||
|
||||
}
|
||||
|
||||
|
||||
public MavenProject getProject()
|
||||
{
|
||||
return project;
|
||||
}
|
||||
|
||||
public void setComplete()
|
||||
{
|
||||
endTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void addWait( MavenProject upstreamProject, ExecutionPlanItem inSchedule, long startWait )
|
||||
{
|
||||
long now = System.currentTimeMillis();
|
||||
dependencies.add( new DependencyLogEntry( upstreamProject, inSchedule, startWait, now, null ) );
|
||||
}
|
||||
|
||||
public void addDependency( MavenProject upstreamProject, String message )
|
||||
{
|
||||
dependencies.add( new DependencyLogEntry( upstreamProject, message ) );
|
||||
}
|
||||
|
||||
public String toString( long rootStart )
|
||||
{
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append( String.format( "%1d %2d ", startTime - rootStart, endTime - rootStart ) );
|
||||
result.append( project.getName() );
|
||||
result.append( " " );
|
||||
result.append( getMojoExecutionDescription( executionPlanItem ) );
|
||||
if ( dependencies.size() > 0 )
|
||||
{
|
||||
result.append( "\n" );
|
||||
for ( DependencyLogEntry waitLogEntry : dependencies )
|
||||
{
|
||||
result.append( " " );
|
||||
result.append( waitLogEntry.toString() );
|
||||
result.append( "\n" );
|
||||
}
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
|
||||
public Object toGraph( long rootStart )
|
||||
{
|
||||
StringBuilder result = new StringBuilder();
|
||||
if ( dependencies.size() > 0 )
|
||||
{
|
||||
for ( DependencyLogEntry waitLogEntry : dependencies )
|
||||
{
|
||||
result.append( " " );
|
||||
result.append( nodeKey( project, executionPlanItem ) );
|
||||
result.append( " -> " );
|
||||
result.append( waitLogEntry.toNodeKey() );
|
||||
result.append( waitLogEntry.toNodeDescription( rootStart ) );
|
||||
result.append( "\n" );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result.append( " " );
|
||||
result.append( nodeKey( project, executionPlanItem ) );
|
||||
result.append( "\n" );
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private static String nodeKey( MavenProject mavenProject, ExecutionPlanItem executionPlanItem )
|
||||
{
|
||||
String key = mavenProject.getArtifactId();
|
||||
if ( executionPlanItem != null )
|
||||
{
|
||||
key += "_" + getMojoExecutionDescription( executionPlanItem );
|
||||
}
|
||||
return key.replace( ".", "_" ).replace( ":", "_" );
|
||||
}
|
||||
|
||||
private static String getMojoExecutionDescription( ExecutionPlanItem executionPlanItem )
|
||||
{
|
||||
if ( executionPlanItem.getMojoExecution() != null )
|
||||
{
|
||||
return executionPlanItem.getMojoExecution().getArtifactId() + getLifeCyclePhase( executionPlanItem );
|
||||
}
|
||||
else
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private static String getLifeCyclePhase( ExecutionPlanItem executionPlanItem )
|
||||
{
|
||||
return executionPlanItem.getLifecyclePhase() != null ? "[" + executionPlanItem.getLifecyclePhase() + "]" : "";
|
||||
}
|
||||
|
||||
|
||||
class DependencyLogEntry
|
||||
{
|
||||
private final ExecutionPlanItem executionPlanItem;
|
||||
|
||||
private final MavenProject upstreamProject;
|
||||
|
||||
private final Long start;
|
||||
|
||||
private final Long stop;
|
||||
|
||||
private final String message;
|
||||
|
||||
DependencyLogEntry( MavenProject upstreamProject, ExecutionPlanItem executionPlanItem, Long start, Long stop,
|
||||
String message )
|
||||
{
|
||||
this.upstreamProject = upstreamProject;
|
||||
this.executionPlanItem = executionPlanItem;
|
||||
this.start = start;
|
||||
this.stop = stop;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
DependencyLogEntry( MavenProject upstreamProject, String message )
|
||||
{
|
||||
this( upstreamProject, null, null, null, message );
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return upstreamProject.getName() + ":" + getExecutionPlanItem() + getElapsed() + getMessage();
|
||||
}
|
||||
|
||||
public String toNodeKey()
|
||||
{
|
||||
return nodeKey( upstreamProject, executionPlanItem );
|
||||
}
|
||||
|
||||
public String toNodeDescription( long rootStart )
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
private String getMessage()
|
||||
{
|
||||
return message != null ? message : "";
|
||||
}
|
||||
|
||||
private String getExecutionPlanItem()
|
||||
{
|
||||
if ( executionPlanItem != null )
|
||||
{
|
||||
return getMojoExecutionDescription( executionPlanItem );
|
||||
}
|
||||
else
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private String getElapsed()
|
||||
{
|
||||
if ( start != null && stop != null )
|
||||
{
|
||||
long elapsed = stop - start;
|
||||
return elapsed > 0 ? ", wait=" + elapsed : "";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,190 +0,0 @@
|
|||
package org.apache.maven.lifecycle.internal;
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import org.apache.maven.InternalErrorException;
|
||||
import org.apache.maven.artifact.Artifact;
|
||||
import org.apache.maven.execution.BuildFailure;
|
||||
import org.apache.maven.execution.ExecutionEvent;
|
||||
import org.apache.maven.execution.MavenExecutionRequest;
|
||||
import org.apache.maven.execution.MavenSession;
|
||||
import org.apache.maven.lifecycle.LifecycleExecutionException;
|
||||
import org.apache.maven.lifecycle.LifecycleNotFoundException;
|
||||
import org.apache.maven.lifecycle.LifecyclePhaseNotFoundException;
|
||||
import org.apache.maven.lifecycle.MavenExecutionPlan;
|
||||
import org.apache.maven.model.Plugin;
|
||||
import org.apache.maven.plugin.InvalidPluginDescriptorException;
|
||||
import org.apache.maven.plugin.MojoNotFoundException;
|
||||
import org.apache.maven.plugin.PluginDescriptorParsingException;
|
||||
import org.apache.maven.plugin.PluginNotFoundException;
|
||||
import org.apache.maven.plugin.PluginResolutionException;
|
||||
import org.apache.maven.plugin.descriptor.MojoDescriptor;
|
||||
import org.apache.maven.plugin.prefix.NoPluginFoundForPrefixException;
|
||||
import org.apache.maven.plugin.version.PluginVersionResolutionException;
|
||||
import org.apache.maven.project.MavenProject;
|
||||
import org.codehaus.plexus.classworlds.realm.ClassRealm;
|
||||
import org.codehaus.plexus.component.annotations.Component;
|
||||
import org.codehaus.plexus.component.annotations.Requirement;
|
||||
import org.codehaus.plexus.logging.Logger;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Common code that is shared by the LifecycleModuleBuilder and the LifeCycleWeaveBuilder
|
||||
*
|
||||
* @since 3.0
|
||||
* @author Kristian Rosenvold
|
||||
* Builds one or more lifecycles for a full module
|
||||
* NOTE: This class is not part of any public api and can be changed or deleted without prior notice.
|
||||
*/
|
||||
@Component( role = BuilderCommon.class )
|
||||
public class BuilderCommon
|
||||
{
|
||||
@Requirement
|
||||
private LifecycleDebugLogger lifecycleDebugLogger;
|
||||
|
||||
@Requirement
|
||||
private LifecycleExecutionPlanCalculator lifeCycleExecutionPlanCalculator;
|
||||
|
||||
@Requirement
|
||||
private ExecutionEventCatapult eventCatapult;
|
||||
|
||||
@Requirement
|
||||
private Logger logger;
|
||||
|
||||
|
||||
public BuilderCommon()
|
||||
{
|
||||
}
|
||||
|
||||
public BuilderCommon( LifecycleDebugLogger lifecycleDebugLogger,
|
||||
LifecycleExecutionPlanCalculator lifeCycleExecutionPlanCalculator, Logger logger )
|
||||
{
|
||||
this.lifecycleDebugLogger = lifecycleDebugLogger;
|
||||
this.lifeCycleExecutionPlanCalculator = lifeCycleExecutionPlanCalculator;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public MavenExecutionPlan resolveBuildPlan( MavenSession session, MavenProject project, TaskSegment taskSegment,
|
||||
Set<Artifact> projectArtifacts )
|
||||
throws PluginNotFoundException, PluginResolutionException, LifecyclePhaseNotFoundException,
|
||||
PluginDescriptorParsingException, MojoNotFoundException, InvalidPluginDescriptorException,
|
||||
NoPluginFoundForPrefixException, LifecycleNotFoundException, PluginVersionResolutionException,
|
||||
LifecycleExecutionException
|
||||
{
|
||||
MavenExecutionPlan executionPlan =
|
||||
lifeCycleExecutionPlanCalculator.calculateExecutionPlan( session, project, taskSegment.getTasks() );
|
||||
|
||||
lifecycleDebugLogger.debugProjectPlan( project, executionPlan );
|
||||
|
||||
if ( session.getRequest().isThreadConfigurationPresent() )
|
||||
{
|
||||
final Set<Plugin> unsafePlugins = executionPlan.getNonThreadSafePlugins();
|
||||
if ( !unsafePlugins.isEmpty() )
|
||||
{
|
||||
logger.warn( "*****************************************************************" );
|
||||
logger.warn( "* Your build is requesting parallel execution, but project *" );
|
||||
logger.warn( "* contains the following plugin(s) that have goals not marked *" );
|
||||
logger.warn( "* as @threadSafe to support parallel building. *" );
|
||||
logger.warn( "* While this /may/ work fine, please look for plugin updates *" );
|
||||
logger.warn( "* and/or request plugins be made thread-safe. *" );
|
||||
logger.warn( "* If reporting an issue, report it against the plugin in *" );
|
||||
logger.warn( "* question, not against maven-core *" );
|
||||
logger.warn( "*****************************************************************" );
|
||||
if ( logger.isDebugEnabled() )
|
||||
{
|
||||
final Set<MojoDescriptor> unsafeGoals = executionPlan.getNonThreadSafeMojos();
|
||||
logger.warn( "The following goals are not marked @threadSafe in " + project.getName() + ":" );
|
||||
for ( MojoDescriptor unsafeGoal : unsafeGoals )
|
||||
{
|
||||
logger.warn( unsafeGoal.getId() );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.warn( "The following plugins are not marked @threadSafe in " + project.getName() + ":" );
|
||||
for ( Plugin unsafePlugin : unsafePlugins )
|
||||
{
|
||||
logger.warn( unsafePlugin.getId() );
|
||||
}
|
||||
logger.warn( "Enable debug to see more precisely which goals are not marked @threadSafe." );
|
||||
}
|
||||
logger.warn( "*****************************************************************" );
|
||||
}
|
||||
}
|
||||
|
||||
return executionPlan;
|
||||
}
|
||||
|
||||
public void handleBuildError( final ReactorContext buildContext, final MavenSession rootSession,
|
||||
final MavenSession currentSession, final MavenProject mavenProject, Exception e,
|
||||
final long buildStartTime )
|
||||
{
|
||||
if ( e instanceof RuntimeException )
|
||||
{
|
||||
e = new InternalErrorException( "Internal error: " + e, e );
|
||||
}
|
||||
|
||||
buildContext.getResult().addException( e );
|
||||
|
||||
long buildEndTime = System.currentTimeMillis();
|
||||
|
||||
buildContext.getResult().addBuildSummary( new BuildFailure( mavenProject, buildEndTime - buildStartTime, e ) );
|
||||
|
||||
eventCatapult.fire( ExecutionEvent.Type.ProjectFailed, currentSession, null, e );
|
||||
|
||||
if ( MavenExecutionRequest.REACTOR_FAIL_NEVER.equals( rootSession.getReactorFailureBehavior() ) )
|
||||
{
|
||||
// continue the build
|
||||
}
|
||||
else if ( MavenExecutionRequest.REACTOR_FAIL_AT_END.equals( rootSession.getReactorFailureBehavior() ) )
|
||||
{
|
||||
// continue the build but ban all projects that depend on the failed one
|
||||
buildContext.getReactorBuildStatus().blackList( mavenProject );
|
||||
}
|
||||
else if ( MavenExecutionRequest.REACTOR_FAIL_FAST.equals( rootSession.getReactorFailureBehavior() ) )
|
||||
{
|
||||
buildContext.getReactorBuildStatus().halt();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalArgumentException(
|
||||
"invalid reactor failure behavior " + rootSession.getReactorFailureBehavior() );
|
||||
}
|
||||
}
|
||||
|
||||
public static void attachToThread( MavenProject currentProject )
|
||||
{
|
||||
ClassRealm projectRealm = currentProject.getClassRealm();
|
||||
if ( projectRealm != null )
|
||||
{
|
||||
Thread.currentThread().setContextClassLoader( projectRealm );
|
||||
}
|
||||
}
|
||||
|
||||
// Todo: I'm really wondering where this method belongs; smells like it should be on MavenProject, but for some reason
|
||||
// it isn't ? This localization is kind-of a code smell.
|
||||
|
||||
public static String getKey( MavenProject project )
|
||||
{
|
||||
return project.getGroupId() + ':' + project.getArtifactId() + ':' + project.getVersion();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,110 +0,0 @@
|
|||
package org.apache.maven.lifecycle.internal;
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import org.apache.maven.execution.ProjectDependencyGraph;
|
||||
import org.apache.maven.project.MavenProject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Presents a view of the Dependency Graph that is suited for concurrent building.
|
||||
*
|
||||
* @since 3.0
|
||||
* @author Kristian Rosenvold
|
||||
* <p/>
|
||||
* NOTE: This class is not part of any public api and can be changed or deleted without prior notice.
|
||||
*/
|
||||
public class ConcurrencyDependencyGraph
|
||||
{
|
||||
|
||||
private final ProjectBuildList projectBuilds;
|
||||
|
||||
private final ProjectDependencyGraph projectDependencyGraph;
|
||||
|
||||
private final HashSet<MavenProject> finishedProjects = new HashSet<MavenProject>();
|
||||
|
||||
|
||||
public ConcurrencyDependencyGraph( ProjectBuildList projectBuilds, ProjectDependencyGraph projectDependencyGraph )
|
||||
{
|
||||
this.projectDependencyGraph = projectDependencyGraph;
|
||||
this.projectBuilds = projectBuilds;
|
||||
}
|
||||
|
||||
|
||||
public int getNumberOfBuilds()
|
||||
{
|
||||
return projectBuilds.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the builds that have no reactor-dependencies
|
||||
*
|
||||
* @return A list of all the initial builds
|
||||
*/
|
||||
|
||||
public List<MavenProject> getRootSchedulableBuilds()
|
||||
{
|
||||
List<MavenProject> result = new ArrayList<MavenProject>();
|
||||
for ( ProjectSegment projectBuild : projectBuilds )
|
||||
{
|
||||
if ( projectDependencyGraph.getUpstreamProjects( projectBuild.getProject(), false ).size() == 0 )
|
||||
{
|
||||
result.add( projectBuild.getProject() );
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the provided project as finished. Returns a list of
|
||||
*
|
||||
* @param mavenProject The project
|
||||
* @return The list of builds that are eligible for starting now that the provided project is done
|
||||
*/
|
||||
public List<MavenProject> markAsFinished( MavenProject mavenProject )
|
||||
{
|
||||
finishedProjects.add( mavenProject );
|
||||
return getSchedulableNewProcesses( mavenProject );
|
||||
}
|
||||
|
||||
private List<MavenProject> getSchedulableNewProcesses( MavenProject finishedProject )
|
||||
{
|
||||
List<MavenProject> result = new ArrayList<MavenProject>();
|
||||
// schedule dependent projects, if all of their requirements are met
|
||||
for ( MavenProject dependentProject : projectDependencyGraph.getDownstreamProjects( finishedProject, false ) )
|
||||
{
|
||||
final List<MavenProject> upstreamProjects =
|
||||
projectDependencyGraph.getUpstreamProjects( dependentProject, false );
|
||||
if ( finishedProjects.containsAll( upstreamProjects ) )
|
||||
{
|
||||
result.add( dependentProject );
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public ProjectBuildList getProjectBuilds()
|
||||
{
|
||||
return projectBuilds;
|
||||
}
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
package org.apache.maven.lifecycle.internal;
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import org.apache.maven.project.MavenProject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Handles all concurrency-related logging.
|
||||
* <p/>
|
||||
* The logging/diagnostic needs of a concurrent build are different from a linear build. This
|
||||
* delta required to analyze a concurrent build is located here.
|
||||
* <p/>
|
||||
* NOTE: This class is not part of any public api and can be changed or deleted without prior notice.
|
||||
*
|
||||
* @since 3.0
|
||||
* @author Kristian Rosenvold
|
||||
*/
|
||||
public class ConcurrentBuildLogger
|
||||
{
|
||||
private final long startTime;
|
||||
|
||||
private final Map<MavenProject, Thread> threadMap = new ConcurrentHashMap<MavenProject, Thread>();
|
||||
|
||||
public ConcurrentBuildLogger()
|
||||
{
|
||||
startTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
|
||||
List<BuildLogItem> items = Collections.synchronizedList( new ArrayList<BuildLogItem>() );
|
||||
|
||||
public BuildLogItem createBuildLogItem( MavenProject project, ExecutionPlanItem current )
|
||||
{
|
||||
threadMap.put( project, Thread.currentThread() );
|
||||
BuildLogItem result = new BuildLogItem( project, current );
|
||||
items.add( result );
|
||||
return result;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder result = new StringBuilder();
|
||||
for ( Map.Entry<MavenProject, Thread> mavenProjectThreadEntry : threadMap.entrySet() )
|
||||
{
|
||||
result.append( mavenProjectThreadEntry.getKey().getName() );
|
||||
result.append( " ran on " );
|
||||
result.append( mavenProjectThreadEntry.getValue().getName() );
|
||||
result.append( "\n" );
|
||||
}
|
||||
|
||||
for ( BuildLogItem builtLogItem : items )
|
||||
{
|
||||
result.append( builtLogItem.toString( startTime ) );
|
||||
result.append( "\n" );
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
public String toGraph()
|
||||
{
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
Map<MavenProject, Collection<BuildLogItem>> multiMap = new HashMap<MavenProject, Collection<BuildLogItem>>();
|
||||
for ( BuildLogItem builtLogItem : items )
|
||||
{
|
||||
MavenProject project = builtLogItem.getProject();
|
||||
Collection<BuildLogItem> bag = multiMap.get( project );
|
||||
if ( bag == null )
|
||||
{
|
||||
bag = new ArrayList<BuildLogItem>();
|
||||
multiMap.put( project, bag );
|
||||
}
|
||||
bag.add( builtLogItem );
|
||||
}
|
||||
|
||||
result.append( "digraph build" );
|
||||
result.append( " {\n " );
|
||||
|
||||
for ( MavenProject mavenProject : multiMap.keySet() )
|
||||
{
|
||||
final Collection<BuildLogItem> builtLogItems = multiMap.get( mavenProject );
|
||||
result.append( " subgraph " );
|
||||
result.append( mavenProject.getArtifactId() );
|
||||
result.append( " {\n" );
|
||||
|
||||
for ( BuildLogItem builtLogItem : builtLogItems )
|
||||
{
|
||||
result.append( builtLogItem.toGraph( startTime ) );
|
||||
}
|
||||
|
||||
result.append( "\n }\n" );
|
||||
}
|
||||
|
||||
result.append( "\n}\n " );
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
package org.apache.maven.lifecycle.internal;
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Knows the phase the current thread is executing.
|
||||
* <p/>
|
||||
* This class is used in weave-mode only , there may be better ways of doing this once the dust settles.
|
||||
*
|
||||
* @since 3.0
|
||||
* @author Kristian Rosenvold
|
||||
*/
|
||||
class CurrentPhaseForThread
|
||||
{
|
||||
private static final InheritableThreadLocal<String> THREAD_PHASE = new InheritableThreadLocal<String>();
|
||||
|
||||
public static void setPhase( String phase )
|
||||
{
|
||||
THREAD_PHASE.set( phase );
|
||||
}
|
||||
|
||||
public static boolean isPhase( String phase )
|
||||
{
|
||||
return phase.equals( THREAD_PHASE.get() );
|
||||
}
|
||||
|
||||
}
|
|
@ -1,177 +0,0 @@
|
|||
package org.apache.maven.lifecycle.internal;
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import org.apache.maven.execution.MavenSession;
|
||||
import org.apache.maven.project.MavenProject;
|
||||
import org.codehaus.plexus.component.annotations.Component;
|
||||
import org.codehaus.plexus.component.annotations.Requirement;
|
||||
import org.codehaus.plexus.logging.Logger;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CompletionService;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
/**
|
||||
* Builds the full lifecycle in weave-mode (phase by phase as opposed to project-by-project)
|
||||
*
|
||||
* @since 3.0
|
||||
* @author Kristian Rosenvold
|
||||
* Builds one or more lifecycles for a full module
|
||||
* <p/>
|
||||
* NOTE: This class is not part of any public api and can be changed or deleted without prior notice.
|
||||
*/
|
||||
@Component( role = LifecycleThreadedBuilder.class )
|
||||
public class LifecycleThreadedBuilder
|
||||
{
|
||||
|
||||
@Requirement
|
||||
private Logger logger;
|
||||
|
||||
@Requirement
|
||||
private LifecycleModuleBuilder lifecycleModuleBuilder;
|
||||
|
||||
|
||||
public LifecycleThreadedBuilder()
|
||||
{
|
||||
}
|
||||
|
||||
public void build( MavenSession session, ReactorContext reactorContext, ProjectBuildList projectBuilds,
|
||||
List<TaskSegment> currentTaskSegment, ConcurrencyDependencyGraph analyzer,
|
||||
CompletionService<ProjectSegment> service )
|
||||
{
|
||||
|
||||
// Currently disabled
|
||||
ThreadOutputMuxer muxer = null; // new ThreadOutputMuxer( analyzer.getProjectBuilds(), System.out );
|
||||
|
||||
for ( TaskSegment taskSegment : currentTaskSegment )
|
||||
{
|
||||
Map<MavenProject, ProjectSegment> projectBuildMap = projectBuilds.selectSegment( taskSegment );
|
||||
try
|
||||
{
|
||||
multiThreadedProjectTaskSegmentBuild( analyzer, reactorContext, session, service, taskSegment,
|
||||
projectBuildMap, muxer );
|
||||
if ( reactorContext.getReactorBuildStatus().isHalted() )
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch ( Exception e )
|
||||
{
|
||||
session.getResult().addException( e );
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void multiThreadedProjectTaskSegmentBuild( ConcurrencyDependencyGraph analyzer,
|
||||
ReactorContext reactorContext, MavenSession rootSession,
|
||||
CompletionService<ProjectSegment> service,
|
||||
TaskSegment taskSegment,
|
||||
Map<MavenProject, ProjectSegment> projectBuildList,
|
||||
ThreadOutputMuxer muxer )
|
||||
{
|
||||
|
||||
// schedule independent projects
|
||||
for ( MavenProject mavenProject : analyzer.getRootSchedulableBuilds() )
|
||||
{
|
||||
ProjectSegment projectSegment = projectBuildList.get( mavenProject );
|
||||
logger.debug( "Scheduling: " + projectSegment.getProject() );
|
||||
Callable<ProjectSegment> cb =
|
||||
createBuildCallable( rootSession, projectSegment, reactorContext, taskSegment, muxer );
|
||||
service.submit( cb );
|
||||
}
|
||||
|
||||
// for each finished project
|
||||
for ( int i = 0; i < analyzer.getNumberOfBuilds(); i++ )
|
||||
{
|
||||
try
|
||||
{
|
||||
ProjectSegment projectBuild = service.take().get();
|
||||
if ( reactorContext.getReactorBuildStatus().isHalted() )
|
||||
{
|
||||
break;
|
||||
}
|
||||
final List<MavenProject> newItemsThatCanBeBuilt =
|
||||
analyzer.markAsFinished( projectBuild.getProject() );
|
||||
for ( MavenProject mavenProject : newItemsThatCanBeBuilt )
|
||||
{
|
||||
ProjectSegment scheduledDependent = projectBuildList.get( mavenProject );
|
||||
logger.debug( "Scheduling: " + scheduledDependent );
|
||||
Callable<ProjectSegment> cb =
|
||||
createBuildCallable( rootSession, scheduledDependent, reactorContext, taskSegment, muxer );
|
||||
service.submit( cb );
|
||||
}
|
||||
}
|
||||
catch ( InterruptedException e )
|
||||
{
|
||||
rootSession.getResult().addException( e );
|
||||
break;
|
||||
}
|
||||
catch ( ExecutionException e )
|
||||
{
|
||||
rootSession.getResult().addException( e );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// cancel outstanding builds (if any) - this can happen if an exception is thrown in above block
|
||||
|
||||
Future<ProjectSegment> unprocessed;
|
||||
while ( ( unprocessed = service.poll() ) != null )
|
||||
{
|
||||
try
|
||||
{
|
||||
unprocessed.get();
|
||||
}
|
||||
catch ( InterruptedException e )
|
||||
{
|
||||
throw new RuntimeException( e );
|
||||
}
|
||||
catch ( ExecutionException e )
|
||||
{
|
||||
throw new RuntimeException( e );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Callable<ProjectSegment> createBuildCallable( final MavenSession rootSession,
|
||||
final ProjectSegment projectBuild,
|
||||
final ReactorContext reactorContext,
|
||||
final TaskSegment taskSegment, final ThreadOutputMuxer muxer )
|
||||
{
|
||||
return new Callable<ProjectSegment>()
|
||||
{
|
||||
public ProjectSegment call()
|
||||
{
|
||||
// muxer.associateThreadWithProjectSegment( projectBuild );
|
||||
lifecycleModuleBuilder.buildProject( projectBuild.getSession(), rootSession, reactorContext,
|
||||
projectBuild.getProject(), taskSegment );
|
||||
// muxer.setThisModuleComplete( projectBuild );
|
||||
|
||||
return projectBuild;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,490 +0,0 @@
|
|||
package org.apache.maven.lifecycle.internal;
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import org.apache.maven.artifact.Artifact;
|
||||
import org.apache.maven.artifact.ArtifactUtils;
|
||||
import org.apache.maven.execution.BuildSuccess;
|
||||
import org.apache.maven.execution.ExecutionEvent;
|
||||
import org.apache.maven.execution.MavenExecutionRequest;
|
||||
import org.apache.maven.execution.MavenSession;
|
||||
import org.apache.maven.lifecycle.LifecycleExecutionException;
|
||||
import org.apache.maven.lifecycle.MavenExecutionPlan;
|
||||
import org.apache.maven.lifecycle.Schedule;
|
||||
import org.apache.maven.project.MavenProject;
|
||||
import org.codehaus.plexus.component.annotations.Component;
|
||||
import org.codehaus.plexus.component.annotations.Requirement;
|
||||
import org.codehaus.plexus.logging.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CompletionService;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorCompletionService;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
/**
|
||||
* Builds the full lifecycle in weave-mode (phase by phase as opposed to project-by-project)
|
||||
* <p/>
|
||||
* NOTE: Weave mode is still experimental. It may be either promoted to first class citizen
|
||||
* at some later point in time, and it may also be removed entirely. Weave mode has much more aggressive
|
||||
* concurrency behaviour than regular threaded mode, and as such is still under test wrt cross platform stability.
|
||||
* <p/>
|
||||
* To remove weave mode from m3, the following should be removed:
|
||||
* ExecutionPlanItem.schedule w/setters and getters
|
||||
* DefaultLifeCycles.getScheduling() and all its use
|
||||
* ReactorArtifactRepository has a reference to isWeave too.
|
||||
* This class and its usage
|
||||
*
|
||||
* @since 3.0
|
||||
* @author Kristian Rosenvold
|
||||
* Builds one or more lifecycles for a full module
|
||||
* <p/>
|
||||
* NOTE: This class is not part of any public api and can be changed or deleted without prior notice.
|
||||
*/
|
||||
@Component( role = LifecycleWeaveBuilder.class )
|
||||
public class LifecycleWeaveBuilder
|
||||
{
|
||||
|
||||
@Requirement
|
||||
private MojoExecutor mojoExecutor;
|
||||
|
||||
@Requirement
|
||||
private BuilderCommon builderCommon;
|
||||
|
||||
@Requirement
|
||||
private Logger logger;
|
||||
|
||||
@Requirement
|
||||
private ExecutionEventCatapult eventCatapult;
|
||||
|
||||
private Map<MavenProject, MavenExecutionPlan> executionPlans = new HashMap<MavenProject, MavenExecutionPlan>();
|
||||
|
||||
|
||||
public LifecycleWeaveBuilder()
|
||||
{
|
||||
}
|
||||
|
||||
public LifecycleWeaveBuilder( MojoExecutor mojoExecutor, BuilderCommon builderCommon, Logger logger,
|
||||
ExecutionEventCatapult eventCatapult )
|
||||
{
|
||||
this.mojoExecutor = mojoExecutor;
|
||||
this.builderCommon = builderCommon;
|
||||
this.logger = logger;
|
||||
this.eventCatapult = eventCatapult;
|
||||
}
|
||||
|
||||
public void build( ProjectBuildList projectBuilds, ReactorContext buildContext, List<TaskSegment> taskSegments,
|
||||
MavenSession session, ExecutorService executor, ReactorBuildStatus reactorBuildStatus )
|
||||
throws ExecutionException, InterruptedException
|
||||
{
|
||||
ConcurrentBuildLogger concurrentBuildLogger = new ConcurrentBuildLogger();
|
||||
CompletionService<ProjectSegment> service = new ExecutorCompletionService<ProjectSegment>( executor );
|
||||
|
||||
try
|
||||
{
|
||||
for ( MavenProject mavenProject : session.getProjects() )
|
||||
{
|
||||
Artifact mainArtifact = mavenProject.getArtifact();
|
||||
if ( mainArtifact != null && !( mainArtifact instanceof ThreadLockedArtifact ) )
|
||||
{
|
||||
ThreadLockedArtifact threadLockedArtifact = new ThreadLockedArtifact( mainArtifact );
|
||||
mavenProject.setArtifact( threadLockedArtifact );
|
||||
}
|
||||
}
|
||||
|
||||
final List<Future<ProjectSegment>> futures = new ArrayList<Future<ProjectSegment>>();
|
||||
final Map<ProjectSegment, Future<MavenExecutionPlan>> plans =
|
||||
new HashMap<ProjectSegment, Future<MavenExecutionPlan>>();
|
||||
|
||||
for ( TaskSegment taskSegment : taskSegments )
|
||||
{
|
||||
ProjectBuildList segmentChunks = projectBuilds.getByTaskSegment( taskSegment );
|
||||
Set<Artifact> projectArtifacts = new HashSet<Artifact>();
|
||||
for ( ProjectSegment segmentChunk : segmentChunks )
|
||||
{
|
||||
Artifact artifact = segmentChunk.getProject().getArtifact();
|
||||
if ( artifact != null )
|
||||
{
|
||||
projectArtifacts.add( artifact );
|
||||
}
|
||||
}
|
||||
for ( ProjectSegment projectBuild : segmentChunks )
|
||||
{
|
||||
plans.put( projectBuild, executor.submit( createEPFuture( projectBuild, projectArtifacts ) ) );
|
||||
}
|
||||
|
||||
for ( ProjectSegment projectSegment : plans.keySet() )
|
||||
{
|
||||
executionPlans.put( projectSegment.getProject(), plans.get( projectSegment ).get() );
|
||||
|
||||
}
|
||||
for ( ProjectSegment projectBuild : segmentChunks )
|
||||
{
|
||||
try
|
||||
{
|
||||
final MavenExecutionPlan executionPlan = plans.get( projectBuild ).get();
|
||||
|
||||
DependencyContext dependencyContext =
|
||||
mojoExecutor.newDependencyContext( session, executionPlan.getMojoExecutions() );
|
||||
|
||||
final Callable<ProjectSegment> projectBuilder =
|
||||
createCallableForBuildingOneFullModule( buildContext, session, reactorBuildStatus,
|
||||
executionPlan, projectBuild, dependencyContext,
|
||||
concurrentBuildLogger );
|
||||
|
||||
futures.add( service.submit( projectBuilder ) );
|
||||
}
|
||||
catch ( Exception e )
|
||||
{
|
||||
throw new ExecutionException( e );
|
||||
}
|
||||
}
|
||||
|
||||
for ( Future<ProjectSegment> buildFuture : futures )
|
||||
{
|
||||
buildFuture.get(); // At this point, this build *is* finished.
|
||||
// Do not leak threads past here or evil gremlins will get you!
|
||||
}
|
||||
futures.clear();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
projectBuilds.closeAll();
|
||||
}
|
||||
logger.info( concurrentBuildLogger.toString() );
|
||||
}
|
||||
|
||||
private Callable<MavenExecutionPlan> createEPFuture( final ProjectSegment projectSegment,
|
||||
final Set<Artifact> projectArtifacts )
|
||||
{
|
||||
return new Callable<MavenExecutionPlan>()
|
||||
{
|
||||
public MavenExecutionPlan call()
|
||||
throws Exception
|
||||
{
|
||||
return builderCommon.resolveBuildPlan( projectSegment.getSession(), projectSegment.getProject(),
|
||||
projectSegment.getTaskSegment(), projectArtifacts );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private Callable<ProjectSegment> createCallableForBuildingOneFullModule( final ReactorContext reactorContext,
|
||||
final MavenSession rootSession,
|
||||
final ReactorBuildStatus reactorBuildStatus,
|
||||
final MavenExecutionPlan executionPlan,
|
||||
final ProjectSegment projectBuild,
|
||||
final DependencyContext dependencyContext,
|
||||
final ConcurrentBuildLogger concurrentBuildLogger )
|
||||
{
|
||||
return new Callable<ProjectSegment>()
|
||||
{
|
||||
public ProjectSegment call()
|
||||
throws Exception
|
||||
{
|
||||
Iterator<ExecutionPlanItem> planItems = executionPlan.iterator();
|
||||
ExecutionPlanItem current = planItems.hasNext() ? planItems.next() : null;
|
||||
ThreadLockedArtifact threadLockedArtifact =
|
||||
(ThreadLockedArtifact) projectBuild.getProject().getArtifact();
|
||||
if ( threadLockedArtifact != null )
|
||||
{
|
||||
threadLockedArtifact.attachToThread();
|
||||
}
|
||||
long buildStartTime = System.currentTimeMillis();
|
||||
|
||||
//muxer.associateThreadWithProjectSegment( projectBuild );
|
||||
|
||||
if ( reactorBuildStatus.isHaltedOrBlacklisted( projectBuild.getProject() ) )
|
||||
{
|
||||
eventCatapult.fire( ExecutionEvent.Type.ProjectSkipped, projectBuild.getSession(), null );
|
||||
return null;
|
||||
}
|
||||
|
||||
eventCatapult.fire( ExecutionEvent.Type.ProjectStarted, projectBuild.getSession(), null );
|
||||
|
||||
Collection<ArtifactLink> dependencyLinks = getUpstreamReactorDependencies( projectBuild );
|
||||
|
||||
try
|
||||
{
|
||||
PhaseRecorder phaseRecorder = new PhaseRecorder( projectBuild.getProject() );
|
||||
long totalMojoTime = 0;
|
||||
long mojoStart;
|
||||
while ( current != null && !reactorBuildStatus.isHaltedOrBlacklisted( projectBuild.getProject() ) )
|
||||
{
|
||||
|
||||
BuildLogItem builtLogItem =
|
||||
concurrentBuildLogger.createBuildLogItem( projectBuild.getProject(), current );
|
||||
final Schedule schedule = current.getSchedule();
|
||||
|
||||
mojoStart = System.currentTimeMillis();
|
||||
buildExecutionPlanItem( current, phaseRecorder, schedule, reactorContext, projectBuild,
|
||||
dependencyContext );
|
||||
totalMojoTime += ( System.currentTimeMillis() - mojoStart );
|
||||
|
||||
current.setComplete();
|
||||
builtLogItem.setComplete();
|
||||
|
||||
ExecutionPlanItem nextPlanItem = planItems.hasNext() ? planItems.next() : null;
|
||||
if ( nextPlanItem != null && phaseRecorder.isDifferentPhase( nextPlanItem.getMojoExecution() ) )
|
||||
{
|
||||
|
||||
final Schedule scheduleOfNext = nextPlanItem.getSchedule();
|
||||
if ( scheduleOfNext == null || !scheduleOfNext.isParallel() )
|
||||
{
|
||||
waitForAppropriateUpstreamExecutionsToFinish( builtLogItem, nextPlanItem, projectBuild,
|
||||
scheduleOfNext );
|
||||
}
|
||||
|
||||
for ( ArtifactLink dependencyLink : dependencyLinks )
|
||||
{
|
||||
dependencyLink.resolveFromUpstream();
|
||||
}
|
||||
}
|
||||
current = nextPlanItem;
|
||||
}
|
||||
|
||||
final BuildSuccess summary =
|
||||
new BuildSuccess( projectBuild.getProject(), totalMojoTime ); // - waitingTime
|
||||
reactorContext.getResult().addBuildSummary( summary );
|
||||
eventCatapult.fire( ExecutionEvent.Type.ProjectSucceeded, projectBuild.getSession(), null );
|
||||
}
|
||||
catch ( Exception e )
|
||||
{
|
||||
builderCommon.handleBuildError( reactorContext, rootSession, projectBuild.getSession(),
|
||||
projectBuild.getProject(), e, buildStartTime );
|
||||
}
|
||||
finally
|
||||
{
|
||||
if ( current != null )
|
||||
{
|
||||
executionPlan.forceAllComplete();
|
||||
}
|
||||
// muxer.setThisModuleComplete( projectBuild );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
private void waitForAppropriateUpstreamExecutionsToFinish( BuildLogItem builtLogItem,
|
||||
ExecutionPlanItem nextPlanItem,
|
||||
ProjectSegment projectBuild, Schedule scheduleOfNext )
|
||||
throws InterruptedException
|
||||
{
|
||||
for ( MavenProject upstreamProject : projectBuild.getImmediateUpstreamProjects() )
|
||||
{
|
||||
final MavenExecutionPlan upstreamPlan = executionPlans.get( upstreamProject );
|
||||
final String nextPhase = scheduleOfNext != null && scheduleOfNext.hasUpstreamPhaseDefined()
|
||||
? scheduleOfNext.getUpstreamPhase()
|
||||
: nextPlanItem.getLifecyclePhase();
|
||||
final ExecutionPlanItem upstream = upstreamPlan.findLastInPhase( nextPhase );
|
||||
|
||||
if ( upstream != null )
|
||||
{
|
||||
long startWait = System.currentTimeMillis();
|
||||
upstream.waitUntilDone();
|
||||
builtLogItem.addWait( upstreamProject, upstream, startWait );
|
||||
}
|
||||
else if ( !upstreamPlan.containsPhase( nextPhase ) )
|
||||
{
|
||||
// Still a bit of a kludge; if we cannot connect in a sensible way to
|
||||
// the upstream build plan we just revert to waiting for it all to
|
||||
// complete. Real problem is per-mojo phase->lifecycle mapping
|
||||
builtLogItem.addDependency( upstreamProject, "No phase tracking possible " );
|
||||
upstreamPlan.waitUntilAllDone();
|
||||
}
|
||||
else
|
||||
{
|
||||
builtLogItem.addDependency( upstreamProject, "No schedule" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Collection<ArtifactLink> getUpstreamReactorDependencies( ProjectSegment projectBuild )
|
||||
{
|
||||
Collection<ArtifactLink> result = new ArrayList<ArtifactLink>();
|
||||
for ( MavenProject upstreamProject : projectBuild.getTransitiveUpstreamProjects() )
|
||||
{
|
||||
Artifact upStreamArtifact = upstreamProject.getArtifact();
|
||||
if ( upStreamArtifact != null )
|
||||
{
|
||||
Artifact dependencyArtifact = findDependency( projectBuild.getProject(), upStreamArtifact );
|
||||
if ( dependencyArtifact != null )
|
||||
{
|
||||
result.add( new ArtifactLink( dependencyArtifact, upStreamArtifact ) );
|
||||
}
|
||||
}
|
||||
|
||||
Artifact upStreamTestScopedArtifact = findTestScopedArtifact( upstreamProject );
|
||||
if ( upStreamTestScopedArtifact != null )
|
||||
{
|
||||
Artifact dependencyArtifact = findDependency( projectBuild.getProject(), upStreamArtifact );
|
||||
if ( dependencyArtifact != null )
|
||||
{
|
||||
result.add( new ArtifactLink( dependencyArtifact, upStreamTestScopedArtifact ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private Artifact findTestScopedArtifact( MavenProject upstreamProject )
|
||||
{
|
||||
if ( upstreamProject == null )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Artifact> artifactList = upstreamProject.getAttachedArtifacts();
|
||||
for ( Artifact artifact : artifactList )
|
||||
{
|
||||
if ( Artifact.SCOPE_TEST.equals( artifact.getScope() ) )
|
||||
{
|
||||
return artifact;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isThreadLockedAndEmpty( Artifact artifact )
|
||||
{
|
||||
return artifact instanceof ThreadLockedArtifact && !( (ThreadLockedArtifact) artifact ).hasReal();
|
||||
}
|
||||
|
||||
private static Artifact findDependency( MavenProject project, Artifact upStreamArtifact )
|
||||
{
|
||||
if ( upStreamArtifact == null || isThreadLockedAndEmpty( upStreamArtifact ) )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
String key = ArtifactUtils.key( upStreamArtifact.getGroupId(), upStreamArtifact.getArtifactId(),
|
||||
upStreamArtifact.getVersion() );
|
||||
final Set<Artifact> deps = project.getDependencyArtifacts();
|
||||
for ( Artifact dep : deps )
|
||||
{
|
||||
String depKey = ArtifactUtils.key( dep.getGroupId(), dep.getArtifactId(), dep.getVersion() );
|
||||
if ( key.equals( depKey ) )
|
||||
{
|
||||
return dep;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
private void buildExecutionPlanItem( ExecutionPlanItem current, PhaseRecorder phaseRecorder, Schedule schedule,
|
||||
ReactorContext reactorContext, ProjectSegment projectBuild,
|
||||
DependencyContext dependencyContext )
|
||||
throws LifecycleExecutionException
|
||||
{
|
||||
if ( schedule != null && schedule.isMojoSynchronized() )
|
||||
{
|
||||
synchronized ( current.getPlugin() )
|
||||
{
|
||||
buildExecutionPlanItem( reactorContext, current, projectBuild, dependencyContext, phaseRecorder );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
buildExecutionPlanItem( reactorContext, current, projectBuild, dependencyContext, phaseRecorder );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void buildExecutionPlanItem( ReactorContext reactorContext, ExecutionPlanItem node,
|
||||
ProjectSegment projectBuild, DependencyContext dependencyContext,
|
||||
PhaseRecorder phaseRecorder )
|
||||
throws LifecycleExecutionException
|
||||
{
|
||||
|
||||
MavenProject currentProject = projectBuild.getProject();
|
||||
|
||||
long buildStartTime = System.currentTimeMillis();
|
||||
|
||||
CurrentPhaseForThread.setPhase( node.getLifecyclePhase() );
|
||||
|
||||
MavenSession sessionForThisModule = projectBuild.getSession();
|
||||
try
|
||||
{
|
||||
|
||||
if ( reactorContext.getReactorBuildStatus().isHaltedOrBlacklisted( currentProject ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
BuilderCommon.attachToThread( currentProject );
|
||||
|
||||
mojoExecutor.execute( sessionForThisModule, node.getMojoExecution(), reactorContext.getProjectIndex(),
|
||||
dependencyContext, phaseRecorder );
|
||||
|
||||
final BuildSuccess summary =
|
||||
new BuildSuccess( currentProject, System.currentTimeMillis() - buildStartTime );
|
||||
reactorContext.getResult().addBuildSummary( summary );
|
||||
}
|
||||
finally
|
||||
{
|
||||
Thread.currentThread().setContextClassLoader( reactorContext.getOriginalContextClassLoader() );
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isWeaveMode( MavenExecutionRequest request )
|
||||
{
|
||||
return "true".equals( request.getUserProperties().getProperty( "maven3.weaveMode" ) );
|
||||
}
|
||||
|
||||
public static void setWeaveMode( Properties properties )
|
||||
{
|
||||
properties.setProperty( "maven3.weaveMode", "true" );
|
||||
}
|
||||
|
||||
static class ArtifactLink
|
||||
{
|
||||
private final Artifact artifactInThis;
|
||||
|
||||
private final Artifact upstream;
|
||||
|
||||
ArtifactLink( Artifact artifactInThis, Artifact upstream )
|
||||
{
|
||||
this.artifactInThis = artifactInThis;
|
||||
this.upstream = upstream;
|
||||
}
|
||||
|
||||
public void resolveFromUpstream()
|
||||
{
|
||||
artifactInThis.setFile( upstream.getFile() );
|
||||
artifactInThis.setRepository( upstream.getRepository() );
|
||||
artifactInThis.setResolved( true ); // Or maybe upstream.isResolved()....
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,111 +0,0 @@
|
|||
package org.apache.maven.lifecycle.internal;
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import org.codehaus.plexus.component.annotations.Component;
|
||||
import org.codehaus.plexus.component.annotations.Requirement;
|
||||
import org.codehaus.plexus.logging.Logger;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
@Component( role = ThreadConfigurationService.class )
|
||||
public class ThreadConfigurationService
|
||||
{
|
||||
@Requirement
|
||||
private Logger logger;
|
||||
|
||||
private final int cpuCores;
|
||||
|
||||
|
||||
public ThreadConfigurationService()
|
||||
{
|
||||
cpuCores = Runtime.getRuntime().availableProcessors();
|
||||
}
|
||||
|
||||
public ThreadConfigurationService( Logger logger, int cpuCores )
|
||||
{
|
||||
this.logger = logger;
|
||||
this.cpuCores = cpuCores;
|
||||
}
|
||||
|
||||
|
||||
public ExecutorService getExecutorService( String threadCountConfiguration, boolean perCoreThreadCount,
|
||||
int largestBuildListSize )
|
||||
{
|
||||
Integer threadCount = getThreadCount( threadCountConfiguration, perCoreThreadCount, largestBuildListSize );
|
||||
return getExecutorService( threadCount );
|
||||
|
||||
|
||||
}
|
||||
|
||||
private ExecutorService getExecutorService( Integer threadCount )
|
||||
{
|
||||
if ( threadCount == null )
|
||||
{
|
||||
logger.info( "Building with unlimited threads" );
|
||||
return Executors.newCachedThreadPool();
|
||||
}
|
||||
|
||||
logger.info( "Building with " + threadCount + " threads" );
|
||||
return Executors.newFixedThreadPool( threadCount );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the thread count to use or null for unlimited threads.
|
||||
*
|
||||
* @param threadCountConfiguration The property passed from the command line.
|
||||
* @param perCoreThreadCount Indicates if the threa count should be scaled per cpu core.
|
||||
* @param largestBuildListSize the size of the largest module list (the number of modules)
|
||||
* @return The number of threads to use or null if unlimited
|
||||
*/
|
||||
|
||||
Integer getThreadCount( String threadCountConfiguration, boolean perCoreThreadCount, int largestBuildListSize )
|
||||
{
|
||||
// Default to a value that is not larger than what we can use ;)
|
||||
float threadCount = Math.min( cpuCores, largestBuildListSize );
|
||||
if ( threadCountConfiguration != null )
|
||||
{
|
||||
try
|
||||
{
|
||||
threadCount = Float.parseFloat( threadCountConfiguration );
|
||||
}
|
||||
catch ( NumberFormatException e )
|
||||
{
|
||||
logger.warn(
|
||||
"Couldn't parse thread count, will default to " + threadCount + ": " + threadCountConfiguration );
|
||||
}
|
||||
}
|
||||
if ( perCoreThreadCount )
|
||||
{
|
||||
threadCount = threadCount * cpuCores;
|
||||
}
|
||||
|
||||
final int endResult = Math.round( threadCount );
|
||||
if ( logger.isDebugEnabled() )
|
||||
{
|
||||
logger.debug( "Thread pool size: " + endResult );
|
||||
}
|
||||
return endResult;
|
||||
}
|
||||
}
|
|
@ -1,320 +0,0 @@
|
|||
package org.apache.maven.lifecycle.internal;
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import org.apache.maven.artifact.Artifact;
|
||||
import org.apache.maven.artifact.handler.ArtifactHandler;
|
||||
import org.apache.maven.artifact.metadata.ArtifactMetadata;
|
||||
import org.apache.maven.artifact.repository.ArtifactRepository;
|
||||
import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
|
||||
import org.apache.maven.artifact.versioning.ArtifactVersion;
|
||||
import org.apache.maven.artifact.versioning.OverConstrainedVersionException;
|
||||
import org.apache.maven.artifact.versioning.VersionRange;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
/**
|
||||
* An artifact that conditionally suspends on getFile for anything but the thread it is locked to.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
class ThreadLockedArtifact
|
||||
implements Artifact
|
||||
{
|
||||
private final Artifact real;
|
||||
|
||||
private final CountDownLatch artifactLocked = new CountDownLatch( 1 );
|
||||
|
||||
ThreadLockedArtifact( Artifact real )
|
||||
{
|
||||
this.real = real;
|
||||
}
|
||||
|
||||
public boolean hasReal()
|
||||
{
|
||||
return real != null
|
||||
&& ( !( real instanceof ThreadLockedArtifact ) || ( (ThreadLockedArtifact) real ).hasReal() );
|
||||
}
|
||||
|
||||
public String getGroupId()
|
||||
{
|
||||
return real.getGroupId();
|
||||
}
|
||||
|
||||
public String getArtifactId()
|
||||
{
|
||||
return real.getArtifactId();
|
||||
}
|
||||
|
||||
public String getVersion()
|
||||
{
|
||||
return real.getVersion();
|
||||
}
|
||||
|
||||
public void setVersion( String version )
|
||||
{
|
||||
real.setVersion( version );
|
||||
}
|
||||
|
||||
public String getScope()
|
||||
{
|
||||
return real.getScope();
|
||||
}
|
||||
|
||||
public String getType()
|
||||
{
|
||||
return real.getType();
|
||||
}
|
||||
|
||||
public String getClassifier()
|
||||
{
|
||||
return real.getClassifier();
|
||||
}
|
||||
|
||||
public boolean hasClassifier()
|
||||
{
|
||||
return real.hasClassifier();
|
||||
}
|
||||
|
||||
private static final InheritableThreadLocal<ThreadLockedArtifact> THREAD_ARTIFACT =
|
||||
new InheritableThreadLocal<ThreadLockedArtifact>();
|
||||
|
||||
public void attachToThread()
|
||||
{
|
||||
THREAD_ARTIFACT.set( this );
|
||||
}
|
||||
|
||||
public File getFile()
|
||||
{
|
||||
final ThreadLockedArtifact lockedArtifact = THREAD_ARTIFACT.get();
|
||||
if ( lockedArtifact != null && this != lockedArtifact && mustLock() )
|
||||
{
|
||||
try
|
||||
{
|
||||
artifactLocked.await();
|
||||
}
|
||||
catch ( InterruptedException e )
|
||||
{
|
||||
// Ignore and go on to real.getFile();
|
||||
}
|
||||
}
|
||||
return real.getFile();
|
||||
}
|
||||
|
||||
private boolean mustLock()
|
||||
{
|
||||
boolean dontNeedLock = CurrentPhaseForThread.isPhase( "compile" ) || CurrentPhaseForThread.isPhase( "test" );
|
||||
return !dontNeedLock;
|
||||
}
|
||||
|
||||
public void setFile( File destination )
|
||||
{
|
||||
if ( destination != null && destination.exists() && destination.isFile() )
|
||||
{
|
||||
artifactLocked.countDown();
|
||||
}
|
||||
real.setFile( destination );
|
||||
}
|
||||
|
||||
public String getBaseVersion()
|
||||
{
|
||||
return real.getBaseVersion();
|
||||
}
|
||||
|
||||
public void setBaseVersion( String baseVersion )
|
||||
{
|
||||
real.setBaseVersion( baseVersion );
|
||||
}
|
||||
|
||||
public String getId()
|
||||
{
|
||||
return real.getId();
|
||||
}
|
||||
|
||||
public String getDependencyConflictId()
|
||||
{
|
||||
return real.getDependencyConflictId();
|
||||
}
|
||||
|
||||
public void addMetadata( ArtifactMetadata metadata )
|
||||
{
|
||||
real.addMetadata( metadata );
|
||||
}
|
||||
|
||||
public Collection<ArtifactMetadata> getMetadataList()
|
||||
{
|
||||
return real.getMetadataList();
|
||||
}
|
||||
|
||||
public void setRepository( ArtifactRepository remoteRepository )
|
||||
{
|
||||
real.setRepository( remoteRepository );
|
||||
}
|
||||
|
||||
public ArtifactRepository getRepository()
|
||||
{
|
||||
return real.getRepository();
|
||||
}
|
||||
|
||||
public void updateVersion( String version, ArtifactRepository localRepository )
|
||||
{
|
||||
real.updateVersion( version, localRepository );
|
||||
}
|
||||
|
||||
public String getDownloadUrl()
|
||||
{
|
||||
return real.getDownloadUrl();
|
||||
}
|
||||
|
||||
public void setDownloadUrl( String downloadUrl )
|
||||
{
|
||||
real.setDownloadUrl( downloadUrl );
|
||||
}
|
||||
|
||||
public ArtifactFilter getDependencyFilter()
|
||||
{
|
||||
return real.getDependencyFilter();
|
||||
}
|
||||
|
||||
public void setDependencyFilter( ArtifactFilter artifactFilter )
|
||||
{
|
||||
real.setDependencyFilter( artifactFilter );
|
||||
}
|
||||
|
||||
public ArtifactHandler getArtifactHandler()
|
||||
{
|
||||
return real.getArtifactHandler();
|
||||
}
|
||||
|
||||
public List<String> getDependencyTrail()
|
||||
{
|
||||
return real.getDependencyTrail();
|
||||
}
|
||||
|
||||
public void setDependencyTrail( List<String> dependencyTrail )
|
||||
{
|
||||
real.setDependencyTrail( dependencyTrail );
|
||||
}
|
||||
|
||||
public void setScope( String scope )
|
||||
{
|
||||
real.setScope( scope );
|
||||
}
|
||||
|
||||
public VersionRange getVersionRange()
|
||||
{
|
||||
return real.getVersionRange();
|
||||
}
|
||||
|
||||
public void setVersionRange( VersionRange newRange )
|
||||
{
|
||||
real.setVersionRange( newRange );
|
||||
}
|
||||
|
||||
public void selectVersion( String version )
|
||||
{
|
||||
real.selectVersion( version );
|
||||
}
|
||||
|
||||
public void setGroupId( String groupId )
|
||||
{
|
||||
real.setGroupId( groupId );
|
||||
}
|
||||
|
||||
public void setArtifactId( String artifactId )
|
||||
{
|
||||
real.setArtifactId( artifactId );
|
||||
}
|
||||
|
||||
public boolean isSnapshot()
|
||||
{
|
||||
return real.isSnapshot();
|
||||
}
|
||||
|
||||
public void setResolved( boolean resolved )
|
||||
{
|
||||
real.setResolved( resolved );
|
||||
}
|
||||
|
||||
public boolean isResolved()
|
||||
{
|
||||
return real.isResolved();
|
||||
}
|
||||
|
||||
public void setResolvedVersion( String version )
|
||||
{
|
||||
real.setResolvedVersion( version );
|
||||
}
|
||||
|
||||
public void setArtifactHandler( ArtifactHandler handler )
|
||||
{
|
||||
real.setArtifactHandler( handler );
|
||||
}
|
||||
|
||||
public boolean isRelease()
|
||||
{
|
||||
return real.isRelease();
|
||||
}
|
||||
|
||||
public void setRelease( boolean release )
|
||||
{
|
||||
real.setRelease( release );
|
||||
}
|
||||
|
||||
public List<ArtifactVersion> getAvailableVersions()
|
||||
{
|
||||
return real.getAvailableVersions();
|
||||
}
|
||||
|
||||
public void setAvailableVersions( List<ArtifactVersion> versions )
|
||||
{
|
||||
real.setAvailableVersions( versions );
|
||||
}
|
||||
|
||||
public boolean isOptional()
|
||||
{
|
||||
return real.isOptional();
|
||||
}
|
||||
|
||||
public void setOptional( boolean optional )
|
||||
{
|
||||
real.setOptional( optional );
|
||||
}
|
||||
|
||||
public ArtifactVersion getSelectedVersion()
|
||||
throws OverConstrainedVersionException
|
||||
{
|
||||
return real.getSelectedVersion();
|
||||
}
|
||||
|
||||
public boolean isSelectedVersionKnown()
|
||||
throws OverConstrainedVersionException
|
||||
{
|
||||
return real.isSelectedVersionKnown();
|
||||
}
|
||||
|
||||
public int compareTo( Artifact o )
|
||||
{
|
||||
return real.compareTo( o );
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
package org.apache.maven.lifecycle.internal;
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import junit.framework.Assert;
|
||||
import junit.framework.TestCase;
|
||||
import org.apache.maven.lifecycle.internal.stub.LoggerStub;
|
||||
|
||||
/**
|
||||
* @author Kristian Rosenvold
|
||||
*/
|
||||
public class ThreadConfigurationServiceTest
|
||||
extends TestCase
|
||||
{
|
||||
public void testGetThreadCount()
|
||||
throws Exception
|
||||
{
|
||||
ThreadConfigurationService threadConfigurationService = new ThreadConfigurationService( new LoggerStub(), 3 );
|
||||
|
||||
Assert.assertEquals( 5, threadConfigurationService.getThreadCount( "1.75", true, 6 ).intValue() );
|
||||
Assert.assertEquals( 6, threadConfigurationService.getThreadCount( "1.84", true, 6 ).intValue() );
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue