mirror of https://github.com/apache/maven.git
[MNG-4224] maven lifecycle participant
Submitted by: Igor Fedorenko git-svn-id: https://svn.apache.org/repos/asf/maven/components/trunk@789993 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
e0d29fe6a3
commit
accb45543f
|
@ -0,0 +1,54 @@
|
||||||
|
package org.apache.maven;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows core extensions to participate in build lifecycle.
|
||||||
|
*
|
||||||
|
* All callback methods (will) follow beforeXXX/afterXXX naming pattern to
|
||||||
|
* indicate at what lifecycle point it is being called.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public abstract class AbstractMavenLifecycleParticipant
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked after all MavenProject instances have been created.
|
||||||
|
*
|
||||||
|
* This callback is intended to allow extensions to manipulate MavenProjects
|
||||||
|
* before they are sorted and actual build execution starts.
|
||||||
|
*/
|
||||||
|
public void afterProjectsRead( MavenSession session ) throws MavenExecutionException
|
||||||
|
{
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked after MavenSession instance has been created.
|
||||||
|
*
|
||||||
|
* This callback is intended to allow extensions to inject execution properties,
|
||||||
|
* activate profiles and perform similar tasks that affect MavenProject
|
||||||
|
* instance construction.
|
||||||
|
*/
|
||||||
|
public void afterSessionStart( MavenSession session ) throws MavenExecutionException
|
||||||
|
{
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -22,7 +22,6 @@ import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -46,6 +45,7 @@ import org.apache.maven.repository.DelegatingLocalArtifactRepository;
|
||||||
import org.codehaus.plexus.PlexusContainer;
|
import org.codehaus.plexus.PlexusContainer;
|
||||||
import org.codehaus.plexus.component.annotations.Component;
|
import org.codehaus.plexus.component.annotations.Component;
|
||||||
import org.codehaus.plexus.component.annotations.Requirement;
|
import org.codehaus.plexus.component.annotations.Requirement;
|
||||||
|
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
|
||||||
import org.codehaus.plexus.logging.Logger;
|
import org.codehaus.plexus.logging.Logger;
|
||||||
import org.codehaus.plexus.util.Os;
|
import org.codehaus.plexus.util.Os;
|
||||||
import org.codehaus.plexus.util.StringUtils;
|
import org.codehaus.plexus.util.StringUtils;
|
||||||
|
@ -87,12 +87,23 @@ public class DefaultMaven
|
||||||
|
|
||||||
request.setLocalRepository( delegatingLocalArtifactRepository );
|
request.setLocalRepository( delegatingLocalArtifactRepository );
|
||||||
|
|
||||||
MavenSession session;
|
MavenSession session = new MavenSession( container, request, result);
|
||||||
|
|
||||||
Map<String,MavenProject> projects;
|
try
|
||||||
|
{
|
||||||
|
for ( AbstractMavenLifecycleParticipant listener : getLifecycleParticipants() )
|
||||||
|
{
|
||||||
|
listener.afterSessionStart( session );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch ( MavenExecutionException e )
|
||||||
|
{
|
||||||
|
return processResult( result, e );
|
||||||
|
}
|
||||||
|
|
||||||
//TODO: optimize for the single project or no project
|
//TODO: optimize for the single project or no project
|
||||||
|
|
||||||
|
List<MavenProject> projects;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
projects = getProjectsForMavenReactor( request );
|
projects = getProjectsForMavenReactor( request );
|
||||||
|
@ -101,7 +112,7 @@ public class DefaultMaven
|
||||||
if ( projects.isEmpty() )
|
if ( projects.isEmpty() )
|
||||||
{
|
{
|
||||||
MavenProject project = projectBuilder.buildStandaloneSuperProject( request.getProjectBuildingRequest() );
|
MavenProject project = projectBuilder.buildStandaloneSuperProject( request.getProjectBuildingRequest() );
|
||||||
projects.put( ArtifactUtils.key( project.getGroupId(), project.getArtifactId(), project.getVersion() ), project );
|
projects.add( project );
|
||||||
request.setProjectPresent( false );
|
request.setProjectPresent( false );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,11 +125,27 @@ public class DefaultMaven
|
||||||
return processResult( result, e );
|
return processResult( result, e );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
session.setProjects( projects );
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ProjectSorter projectSorter = new ProjectSorter( projects.values() );
|
for ( AbstractMavenLifecycleParticipant listener : getLifecycleParticipants() )
|
||||||
|
{
|
||||||
|
listener.afterProjectsRead( session );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch ( MavenExecutionException e )
|
||||||
|
{
|
||||||
|
return processResult( result, e );
|
||||||
|
}
|
||||||
|
|
||||||
session = new MavenSession( container, request, result, projectSorter.getSortedProjects() );
|
try
|
||||||
|
{
|
||||||
|
ProjectSorter projectSorter = new ProjectSorter( session.getProjects() );
|
||||||
|
|
||||||
|
projects = projectSorter.getSortedProjects();
|
||||||
|
|
||||||
|
session.setProjects( projects );
|
||||||
}
|
}
|
||||||
catch ( CycleDetectedException e )
|
catch ( CycleDetectedException e )
|
||||||
{
|
{
|
||||||
|
@ -138,8 +165,14 @@ public class DefaultMaven
|
||||||
// Reactor
|
// Reactor
|
||||||
// Workspace
|
// Workspace
|
||||||
// User Local Repository
|
// User Local Repository
|
||||||
|
try
|
||||||
delegatingLocalArtifactRepository.setBuildReactor( new ReactorArtifactRepository( projects ) );
|
{
|
||||||
|
delegatingLocalArtifactRepository.setBuildReactor( new ReactorArtifactRepository( getProjectMap( session.getProjects() ) ) );
|
||||||
|
}
|
||||||
|
catch ( MavenExecutionException e )
|
||||||
|
{
|
||||||
|
return processResult( result, e );
|
||||||
|
}
|
||||||
|
|
||||||
if ( result.hasExceptions() )
|
if ( result.hasExceptions() )
|
||||||
{
|
{
|
||||||
|
@ -162,6 +195,22 @@ public class DefaultMaven
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<AbstractMavenLifecycleParticipant> getLifecycleParticipants()
|
||||||
|
{
|
||||||
|
// TODO injection of component lists does not work
|
||||||
|
List<AbstractMavenLifecycleParticipant> lifecycleListeners;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
lifecycleListeners = container.lookupList( AbstractMavenLifecycleParticipant.class );
|
||||||
|
}
|
||||||
|
catch ( ComponentLookupException e1 )
|
||||||
|
{
|
||||||
|
// this is just silly, lookupList should return an empty list!
|
||||||
|
lifecycleListeners = new ArrayList<AbstractMavenLifecycleParticipant>();
|
||||||
|
}
|
||||||
|
return lifecycleListeners;
|
||||||
|
}
|
||||||
|
|
||||||
private MavenExecutionResult processResult( MavenExecutionResult result, Exception e )
|
private MavenExecutionResult processResult( MavenExecutionResult result, Exception e )
|
||||||
{
|
{
|
||||||
ExceptionHandler handler = new DefaultExceptionHandler();
|
ExceptionHandler handler = new DefaultExceptionHandler();
|
||||||
|
@ -175,14 +224,14 @@ public class DefaultMaven
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Map<String,MavenProject> getProjectsForMavenReactor( MavenExecutionRequest request )
|
private List<MavenProject> getProjectsForMavenReactor( MavenExecutionRequest request )
|
||||||
throws MavenExecutionException, ProjectBuildingException
|
throws MavenExecutionException, ProjectBuildingException
|
||||||
{
|
{
|
||||||
// We have no POM file.
|
// We have no POM file.
|
||||||
//
|
//
|
||||||
if ( request.getPom() == null || !request.getPom().exists() )
|
if ( request.getPom() == null || !request.getPom().exists() )
|
||||||
{
|
{
|
||||||
return new HashMap<String,MavenProject>();
|
return new ArrayList<MavenProject>();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<File> files = Arrays.asList( request.getPom().getAbsoluteFile() );
|
List<File> files = Arrays.asList( request.getPom().getAbsoluteFile() );
|
||||||
|
@ -191,6 +240,12 @@ public class DefaultMaven
|
||||||
|
|
||||||
collectProjects( projects, files, request );
|
collectProjects( projects, files, request );
|
||||||
|
|
||||||
|
return projects;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, MavenProject> getProjectMap( List<MavenProject> projects )
|
||||||
|
throws org.apache.maven.DuplicateProjectException
|
||||||
|
{
|
||||||
Map<String, MavenProject> index = new LinkedHashMap<String, MavenProject>();
|
Map<String, MavenProject> index = new LinkedHashMap<String, MavenProject>();
|
||||||
Map<String, List<File>> collisions = new LinkedHashMap<String, List<File>>();
|
Map<String, List<File>> collisions = new LinkedHashMap<String, List<File>>();
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,6 @@ import org.apache.maven.project.MavenProject;
|
||||||
import org.apache.maven.project.ProjectBuildingRequest;
|
import org.apache.maven.project.ProjectBuildingRequest;
|
||||||
import org.apache.maven.settings.Settings;
|
import org.apache.maven.settings.Settings;
|
||||||
import org.codehaus.plexus.PlexusContainer;
|
import org.codehaus.plexus.PlexusContainer;
|
||||||
import org.codehaus.plexus.util.dag.CycleDetectedException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Jason van Zyl
|
* @author Jason van Zyl
|
||||||
|
@ -55,18 +54,30 @@ public class MavenSession
|
||||||
|
|
||||||
private MavenProject topLevelProject;
|
private MavenProject topLevelProject;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public MavenSession( PlexusContainer container, MavenExecutionRequest request, MavenExecutionResult result, MavenProject project )
|
public MavenSession( PlexusContainer container, MavenExecutionRequest request, MavenExecutionResult result, MavenProject project )
|
||||||
throws CycleDetectedException, DuplicateProjectException
|
|
||||||
{
|
{
|
||||||
this( container, request, result, Arrays.asList( new MavenProject[]{ project } ) );
|
this( container, request, result, Arrays.asList( new MavenProject[]{ project } ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public MavenSession( PlexusContainer container, MavenExecutionRequest request, MavenExecutionResult result, List<MavenProject> projects )
|
public MavenSession( PlexusContainer container, MavenExecutionRequest request, MavenExecutionResult result, List<MavenProject> projects )
|
||||||
throws CycleDetectedException, DuplicateProjectException
|
|
||||||
{
|
{
|
||||||
this.container = container;
|
this.container = container;
|
||||||
this.request = request;
|
this.request = request;
|
||||||
this.result = result;
|
this.result = result;
|
||||||
|
setProjects( projects );
|
||||||
|
}
|
||||||
|
|
||||||
|
public MavenSession( PlexusContainer container, MavenExecutionRequest request, MavenExecutionResult result )
|
||||||
|
{
|
||||||
|
this.container = container;
|
||||||
|
this.request = request;
|
||||||
|
this.result = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProjects( List<MavenProject> projects )
|
||||||
|
{
|
||||||
//TODO: Current for testing classes creating the session
|
//TODO: Current for testing classes creating the session
|
||||||
if ( projects.size() > 0 )
|
if ( projects.size() > 0 )
|
||||||
{
|
{
|
||||||
|
|
|
@ -806,9 +806,12 @@ public class DefaultLifecycleExecutor
|
||||||
{
|
{
|
||||||
String phase = goalsForLifecyclePhase.getKey();
|
String phase = goalsForLifecyclePhase.getKey();
|
||||||
String goals = goalsForLifecyclePhase.getValue();
|
String goals = goalsForLifecyclePhase.getValue();
|
||||||
|
if ( goals != null )
|
||||||
|
{
|
||||||
parseLifecyclePhaseDefinitions( plugins, phase, goals );
|
parseLifecyclePhaseDefinitions( plugins, phase, goals );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else if ( lifecycle.getDefaultPhases() != null )
|
else if ( lifecycle.getDefaultPhases() != null )
|
||||||
{
|
{
|
||||||
for ( String goals : lifecycle.getDefaultPhases() )
|
for ( String goals : lifecycle.getDefaultPhases() )
|
||||||
|
|
|
@ -222,7 +222,7 @@ public class DefaultPluginManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private PluginDescriptor parsebuildPluginDescriptor( InputStream is )
|
public PluginDescriptor parsebuildPluginDescriptor( InputStream is )
|
||||||
throws IOException, PlexusConfigurationException
|
throws IOException, PlexusConfigurationException
|
||||||
{
|
{
|
||||||
PluginDescriptor pluginDescriptor;
|
PluginDescriptor pluginDescriptor;
|
||||||
|
|
|
@ -165,6 +165,8 @@ public class MavenProject
|
||||||
|
|
||||||
private File parentFile;
|
private File parentFile;
|
||||||
|
|
||||||
|
private Map<String, Object> context;
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
||||||
public MavenProject()
|
public MavenProject()
|
||||||
|
@ -1966,4 +1968,41 @@ public class MavenProject
|
||||||
{
|
{
|
||||||
return groupId + ":" + artifactId + ":" + version;
|
return groupId + ":" + artifactId + ":" + version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value of the context value of this project identified
|
||||||
|
* by the given key. If the supplied value is <code>null</code>,
|
||||||
|
* the context value is removed from this project.
|
||||||
|
*
|
||||||
|
* Context values are intended to allow core extensions to associate
|
||||||
|
* derived state with project instances.
|
||||||
|
*/
|
||||||
|
public void setContextValue( String key, Object value )
|
||||||
|
{
|
||||||
|
if ( context == null )
|
||||||
|
{
|
||||||
|
context = new HashMap<String, Object>();
|
||||||
|
}
|
||||||
|
if ( value != null )
|
||||||
|
{
|
||||||
|
context.put( key, value );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.remove( key );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns context value of this project associated with the given key
|
||||||
|
* or null if this project has no such value.
|
||||||
|
*/
|
||||||
|
public Object getContextValue( String key )
|
||||||
|
{
|
||||||
|
if ( context == null )
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return context.get( key );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,111 @@
|
||||||
|
package org.apache.maven;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import org.apache.maven.artifact.Artifact;
|
||||||
|
import org.apache.maven.execution.MavenExecutionRequest;
|
||||||
|
import org.apache.maven.execution.MavenExecutionResult;
|
||||||
|
import org.apache.maven.execution.MavenSession;
|
||||||
|
import org.apache.maven.model.Dependency;
|
||||||
|
import org.apache.maven.project.MavenProject;
|
||||||
|
import org.codehaus.plexus.PlexusContainer;
|
||||||
|
import org.codehaus.plexus.component.repository.ComponentDescriptor;
|
||||||
|
|
||||||
|
public class MavenLifecycleParticipantTest
|
||||||
|
extends AbstractCoreMavenComponentTestCase
|
||||||
|
{
|
||||||
|
|
||||||
|
private static final String INJECTED_ARTIFACT_ID = "injected";
|
||||||
|
|
||||||
|
public static class InjectDependencyLifecycleListener
|
||||||
|
extends AbstractMavenLifecycleParticipant
|
||||||
|
{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterProjectsRead( MavenSession session )
|
||||||
|
{
|
||||||
|
MavenProject project = session.getProjects().get( 0 );
|
||||||
|
|
||||||
|
Dependency dependency = new Dependency();
|
||||||
|
dependency.setArtifactId( INJECTED_ARTIFACT_ID );
|
||||||
|
dependency.setGroupId( "foo" );
|
||||||
|
dependency.setVersion( "1.2.3" );
|
||||||
|
dependency.setScope( "system" );
|
||||||
|
try
|
||||||
|
{
|
||||||
|
dependency.setSystemPath( new File(
|
||||||
|
"src/test/projects/lifecycle-executor/project-with-additional-lifecycle-elements/pom.xml" ).getCanonicalPath() );
|
||||||
|
}
|
||||||
|
catch ( IOException e )
|
||||||
|
{
|
||||||
|
throw new RuntimeException( e );
|
||||||
|
}
|
||||||
|
|
||||||
|
project.getModel().addDependency( dependency );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterSessionStart( MavenSession session )
|
||||||
|
{
|
||||||
|
session.getExecutionProperties().setProperty( "injected", "bar" );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setupContainer()
|
||||||
|
{
|
||||||
|
super.setupContainer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getProjectsDirectory()
|
||||||
|
{
|
||||||
|
return "src/test/projects/lifecycle-listener";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testDependencyInjection()
|
||||||
|
throws Exception
|
||||||
|
{
|
||||||
|
PlexusContainer container = getContainer();
|
||||||
|
|
||||||
|
ComponentDescriptor cd =
|
||||||
|
new ComponentDescriptor( InjectDependencyLifecycleListener.class, container.getContainerRealm() );
|
||||||
|
cd.setRoleClass( AbstractMavenLifecycleParticipant.class );
|
||||||
|
container.addComponentDescriptor( cd );
|
||||||
|
|
||||||
|
Maven maven = container.lookup( Maven.class );
|
||||||
|
File pom = getProject( "lifecycle-listener-dependency-injection" );
|
||||||
|
MavenExecutionRequest request = createMavenExecutionRequest( pom );
|
||||||
|
MavenExecutionResult result = maven.execute( request );
|
||||||
|
|
||||||
|
assertFalse( result.hasExceptions() );
|
||||||
|
|
||||||
|
MavenProject project = result.getProject();
|
||||||
|
|
||||||
|
assertEquals( "bar", project.getProperties().getProperty( "foo" ) );
|
||||||
|
|
||||||
|
ArrayList<Artifact> artifacts = new ArrayList<Artifact>( project.getArtifacts() );
|
||||||
|
|
||||||
|
assertEquals( 1, artifacts.size() );
|
||||||
|
assertEquals( INJECTED_ARTIFACT_ID, artifacts.get( 0 ).getArtifactId() );
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,7 +6,6 @@ import org.apache.maven.exception.ExceptionHandler;
|
||||||
import org.apache.maven.exception.ExceptionSummary;
|
import org.apache.maven.exception.ExceptionSummary;
|
||||||
import org.apache.maven.execution.MavenExecutionRequest;
|
import org.apache.maven.execution.MavenExecutionRequest;
|
||||||
import org.apache.maven.execution.MavenExecutionResult;
|
import org.apache.maven.execution.MavenExecutionResult;
|
||||||
import org.apache.maven.execution.MavenSession;
|
|
||||||
import org.codehaus.plexus.component.annotations.Requirement;
|
import org.codehaus.plexus.component.annotations.Requirement;
|
||||||
|
|
||||||
public class MavenTest
|
public class MavenTest
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
<project>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>org.apache.maven.lifecycle-listener.test</groupId>
|
||||||
|
<artifactId>simple</artifactId>
|
||||||
|
<version>1.0</version>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<foo>${injected}</foo>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
</project>
|
Loading…
Reference in New Issue