Skip to content

Commit e5e1a87

Browse files
brkyvzmtbrandy
authored andcommitted
[SPARK-8095] Resolve dependencies of --packages in local ivy cache
Dependencies of artifacts in the local ivy cache were not being resolved properly. The dependencies were not being picked up. Now they should be. cc andrewor14 Author: Burak Yavuz <[email protected]> Closes apache#6788 from brkyvz/local-ivy-fix and squashes the following commits: 2875bf4 [Burak Yavuz] fix temp dir bug 48cc648 [Burak Yavuz] improve deletion a69e3e6 [Burak Yavuz] delete cache before test as well 0037197 [Burak Yavuz] fix merge conflicts f60772c [Burak Yavuz] use different folder for m2 cache during testing b6ef038 [Burak Yavuz] [SPARK-8095] Resolve dependencies of Spark Packages in local ivy cache Conflicts: core/src/test/scala/org/apache/spark/deploy/SparkSubmitUtilsSuite.scala
1 parent 722ff96 commit e5e1a87

File tree

3 files changed

+135
-33
lines changed

3 files changed

+135
-33
lines changed

core/src/main/scala/org/apache/spark/deploy/SparkSubmit.scala

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ import org.apache.ivy.core.resolve.ResolveOptions
3535
import org.apache.ivy.core.retrieve.RetrieveOptions
3636
import org.apache.ivy.core.settings.IvySettings
3737
import org.apache.ivy.plugins.matcher.GlobPatternMatcher
38-
import org.apache.ivy.plugins.resolver.{ChainResolver, IBiblioResolver}
38+
import org.apache.ivy.plugins.repository.file.FileRepository
39+
import org.apache.ivy.plugins.resolver.{FileSystemResolver, ChainResolver, IBiblioResolver}
3940
import org.apache.spark.SPARK_VERSION
4041
import org.apache.spark.deploy.rest._
4142
import org.apache.spark.util.{ChildFirstURLClassLoader, MutableURLClassLoader, Utils}
@@ -779,8 +780,14 @@ private[spark] object SparkSubmitUtils {
779780
}
780781

781782
/** Path of the local Maven cache. */
782-
private[spark] def m2Path: File = new File(System.getProperty("user.home"),
783-
".m2" + File.separator + "repository" + File.separator)
783+
private[spark] def m2Path: File = {
784+
if (Utils.isTesting) {
785+
// test builds delete the maven cache, and this can cause flakiness
786+
new File("dummy", ".m2" + File.separator + "repository")
787+
} else {
788+
new File(System.getProperty("user.home"), ".m2" + File.separator + "repository")
789+
}
790+
}
784791

785792
/**
786793
* Extracts maven coordinates from a comma-delimited string
@@ -800,12 +807,13 @@ private[spark] object SparkSubmitUtils {
800807
localM2.setName("local-m2-cache")
801808
cr.add(localM2)
802809

803-
val localIvy = new IBiblioResolver
804-
localIvy.setRoot(new File(ivySettings.getDefaultIvyUserDir,
805-
"local" + File.separator).toURI.toString)
810+
val localIvy = new FileSystemResolver
811+
val localIvyRoot = new File(ivySettings.getDefaultIvyUserDir, "local")
812+
localIvy.setLocal(true)
813+
localIvy.setRepository(new FileRepository(localIvyRoot))
806814
val ivyPattern = Seq("[organisation]", "[module]", "[revision]", "[type]s",
807815
"[artifact](-[classifier]).[ext]").mkString(File.separator)
808-
localIvy.setPattern(ivyPattern)
816+
localIvy.addIvyPattern(localIvyRoot.getAbsolutePath + File.separator + ivyPattern)
809817
localIvy.setName("local-ivy-cache")
810818
cr.add(localIvy)
811819

core/src/test/scala/org/apache/spark/deploy/IvyTestUtils.scala

Lines changed: 106 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import com.google.common.io.{Files, ByteStreams}
2424

2525
import org.apache.commons.io.FileUtils
2626

27+
import org.apache.ivy.core.settings.IvySettings
28+
2729
import org.apache.spark.TestUtils.{createCompiledClass, JavaSourceFromString}
2830
import org.apache.spark.deploy.SparkSubmitUtils.MavenCoordinate
2931

@@ -44,13 +46,30 @@ private[deploy] object IvyTestUtils {
4446
if (!useIvyLayout) {
4547
Seq(groupDirs, artifactDirs, artifact.version).mkString(File.separator)
4648
} else {
47-
Seq(groupDirs, artifactDirs, artifact.version, ext + "s").mkString(File.separator)
49+
Seq(artifact.groupId, artifactDirs, artifact.version, ext + "s").mkString(File.separator)
4850
}
4951
new File(prefix, artifactPath)
5052
}
5153

52-
private def artifactName(artifact: MavenCoordinate, ext: String = ".jar"): String = {
53-
s"${artifact.artifactId}-${artifact.version}$ext"
54+
/** Returns the artifact naming based on standard ivy or maven format. */
55+
private def artifactName(
56+
artifact: MavenCoordinate,
57+
useIvyLayout: Boolean,
58+
ext: String = ".jar"): String = {
59+
if (!useIvyLayout) {
60+
s"${artifact.artifactId}-${artifact.version}$ext"
61+
} else {
62+
s"${artifact.artifactId}$ext"
63+
}
64+
}
65+
66+
/** Returns the directory for the given groupId based on standard ivy or maven format. */
67+
private def getBaseGroupDirectory(artifact: MavenCoordinate, useIvyLayout: Boolean): String = {
68+
if (!useIvyLayout) {
69+
artifact.groupId.replace(".", File.separator)
70+
} else {
71+
artifact.groupId
72+
}
5473
}
5574

5675
/** Write the contents to a file to the supplied directory. */
@@ -92,6 +111,22 @@ private[deploy] object IvyTestUtils {
92111
createCompiledClass(className, dir, sourceFile, Seq.empty)
93112
}
94113

114+
private def createDescriptor(
115+
tempPath: File,
116+
artifact: MavenCoordinate,
117+
dependencies: Option[Seq[MavenCoordinate]],
118+
useIvyLayout: Boolean): File = {
119+
if (useIvyLayout) {
120+
val ivyXmlPath = pathFromCoordinate(artifact, tempPath, "ivy", true)
121+
Files.createParentDirs(new File(ivyXmlPath, "dummy"))
122+
createIvyDescriptor(ivyXmlPath, artifact, dependencies)
123+
} else {
124+
val pomPath = pathFromCoordinate(artifact, tempPath, "pom", useIvyLayout)
125+
Files.createParentDirs(new File(pomPath, "dummy"))
126+
createPom(pomPath, artifact, dependencies)
127+
}
128+
}
129+
95130
/** Helper method to write artifact information in the pom. */
96131
private def pomArtifactWriter(artifact: MavenCoordinate, tabCount: Int = 1): String = {
97132
var result = "\n" + " " * tabCount + s"<groupId>${artifact.groupId}</groupId>"
@@ -121,15 +156,55 @@ private[deploy] object IvyTestUtils {
121156
"\n <dependencies>\n" + inside + "\n </dependencies>"
122157
}.getOrElse("")
123158
content += "\n</project>"
124-
writeFile(dir, artifactName(artifact, ".pom"), content.trim)
159+
writeFile(dir, artifactName(artifact, false, ".pom"), content.trim)
160+
}
161+
162+
/** Helper method to write artifact information in the ivy.xml. */
163+
private def ivyArtifactWriter(artifact: MavenCoordinate): String = {
164+
s"""<dependency org="${artifact.groupId}" name="${artifact.artifactId}"
165+
| rev="${artifact.version}" force="true"
166+
| conf="compile->compile(*),master(*);runtime->runtime(*)"/>""".stripMargin
167+
}
168+
169+
/** Create a pom file for this artifact. */
170+
private def createIvyDescriptor(
171+
dir: File,
172+
artifact: MavenCoordinate,
173+
dependencies: Option[Seq[MavenCoordinate]]): File = {
174+
var content = s"""
175+
|<?xml version="1.0" encoding="UTF-8"?>
176+
|<ivy-module version="2.0" xmlns:m="http://ant.apache.org/ivy/maven">
177+
| <info organisation="${artifact.groupId}"
178+
| module="${artifact.artifactId}"
179+
| revision="${artifact.version}"
180+
| status="release" publication="20150405222456" />
181+
| <configurations>
182+
| <conf name="default" visibility="public" description="" extends="runtime,master"/>
183+
| <conf name="compile" visibility="public" description=""/>
184+
| <conf name="master" visibility="public" description=""/>
185+
| <conf name="runtime" visibility="public" description="" extends="compile"/>
186+
| <conf name="pom" visibility="public" description=""/>
187+
| </configurations>
188+
| <publications>
189+
| <artifact name="${artifactName(artifact, true, "")}" type="jar" ext="jar"
190+
| conf="master"/>
191+
| </publications>
192+
""".stripMargin.trim
193+
content += dependencies.map { deps =>
194+
val inside = deps.map(ivyArtifactWriter).mkString("\n")
195+
"\n <dependencies>\n" + inside + "\n </dependencies>"
196+
}.getOrElse("")
197+
content += "\n</ivy-module>"
198+
writeFile(dir, "ivy.xml", content.trim)
125199
}
126200

127201
/** Create the jar for the given maven coordinate, using the supplied files. */
128202
private def packJar(
129203
dir: File,
130204
artifact: MavenCoordinate,
131-
files: Seq[(String, File)]): File = {
132-
val jarFile = new File(dir, artifactName(artifact))
205+
files: Seq[(String, File)],
206+
useIvyLayout: Boolean): File = {
207+
val jarFile = new File(dir, artifactName(artifact, useIvyLayout))
133208
val jarFileStream = new FileOutputStream(jarFile)
134209
val jarStream = new JarOutputStream(jarFileStream, new java.util.jar.Manifest())
135210

@@ -187,12 +262,10 @@ private[deploy] object IvyTestUtils {
187262
} else {
188263
Seq(javaFile)
189264
}
190-
val jarFile = packJar(jarPath, artifact, allFiles)
265+
val jarFile = packJar(jarPath, artifact, allFiles, useIvyLayout)
191266
assert(jarFile.exists(), "Problem creating Jar file")
192-
val pomPath = pathFromCoordinate(artifact, tempPath, "pom", useIvyLayout)
193-
Files.createParentDirs(new File(pomPath, "dummy"))
194-
val pomFile = createPom(pomPath, artifact, dependencies)
195-
assert(pomFile.exists(), "Problem creating Pom file")
267+
val descriptor = createDescriptor(tempPath, artifact, dependencies, useIvyLayout)
268+
assert(descriptor.exists(), "Problem creating Pom file")
196269
} finally {
197270
FileUtils.deleteDirectory(root)
198271
}
@@ -237,25 +310,40 @@ private[deploy] object IvyTestUtils {
237310
dependencies: Option[String],
238311
rootDir: Option[File],
239312
useIvyLayout: Boolean = false,
240-
withPython: Boolean = false)(f: String => Unit): Unit = {
313+
withPython: Boolean = false,
314+
ivySettings: IvySettings = new IvySettings)(f: String => Unit): Unit = {
315+
val deps = dependencies.map(SparkSubmitUtils.extractMavenCoordinates)
316+
purgeLocalIvyCache(artifact, deps, ivySettings)
241317
val repo = createLocalRepositoryForTests(artifact, dependencies, rootDir, useIvyLayout,
242318
withPython)
243319
try {
244320
f(repo.toURI.toString)
245321
} finally {
246322
// Clean up
247323
if (repo.toString.contains(".m2") || repo.toString.contains(".ivy2")) {
248-
FileUtils.deleteDirectory(new File(repo,
249-
artifact.groupId.replace(".", File.separator) + File.separator + artifact.artifactId))
250-
dependencies.map(SparkSubmitUtils.extractMavenCoordinates).foreach { seq =>
251-
seq.foreach { dep =>
252-
FileUtils.deleteDirectory(new File(repo,
253-
dep.artifactId.replace(".", File.separator)))
324+
val groupDir = getBaseGroupDirectory(artifact, useIvyLayout)
325+
FileUtils.deleteDirectory(new File(repo, groupDir + File.separator + artifact.artifactId))
326+
deps.foreach { _.foreach { dep =>
327+
FileUtils.deleteDirectory(new File(repo, getBaseGroupDirectory(dep, useIvyLayout)))
254328
}
255329
}
256330
} else {
257331
FileUtils.deleteDirectory(repo)
258332
}
333+
purgeLocalIvyCache(artifact, deps, ivySettings)
334+
}
335+
}
336+
337+
/** Deletes the test packages from the ivy cache */
338+
private def purgeLocalIvyCache(
339+
artifact: MavenCoordinate,
340+
dependencies: Option[Seq[MavenCoordinate]],
341+
ivySettings: IvySettings): Unit = {
342+
// delete the artifact from the cache as well if it already exists
343+
FileUtils.deleteDirectory(new File(ivySettings.getDefaultCache, artifact.groupId))
344+
dependencies.foreach { _.foreach { dep =>
345+
FileUtils.deleteDirectory(new File(ivySettings.getDefaultCache, dep.groupId))
346+
}
259347
}
260348
}
261349
}

core/src/test/scala/org/apache/spark/deploy/SparkSubmitUtilsSuite.scala

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import org.scalatest.{BeforeAndAfterAll, FunSuite}
2424

2525
import org.apache.ivy.core.module.descriptor.MDArtifact
2626
import org.apache.ivy.core.settings.IvySettings
27-
import org.apache.ivy.plugins.resolver.IBiblioResolver
27+
import org.apache.ivy.plugins.resolver.{AbstractResolver, FileSystemResolver, IBiblioResolver}
2828

2929
import org.apache.spark.deploy.SparkSubmitUtils.MavenCoordinate
3030
import org.apache.spark.util.Utils
@@ -67,18 +67,18 @@ class SparkSubmitUtilsSuite extends FunSuite with BeforeAndAfterAll {
6767
// should have central and spark-packages by default
6868
assert(res1.getResolvers.size() === 4)
6969
assert(res1.getResolvers.get(0).asInstanceOf[IBiblioResolver].getName === "local-m2-cache")
70-
assert(res1.getResolvers.get(1).asInstanceOf[IBiblioResolver].getName === "local-ivy-cache")
70+
assert(res1.getResolvers.get(1).asInstanceOf[FileSystemResolver].getName === "local-ivy-cache")
7171
assert(res1.getResolvers.get(2).asInstanceOf[IBiblioResolver].getName === "central")
7272
assert(res1.getResolvers.get(3).asInstanceOf[IBiblioResolver].getName === "spark-packages")
7373

7474
val repos = "a/1,b/2,c/3"
7575
val resolver2 = SparkSubmitUtils.createRepoResolvers(Option(repos), settings)
7676
assert(resolver2.getResolvers.size() === 7)
7777
val expected = repos.split(",").map(r => s"$r/")
78-
resolver2.getResolvers.toArray.zipWithIndex.foreach { case (resolver: IBiblioResolver, i) =>
78+
resolver2.getResolvers.toArray.zipWithIndex.foreach { case (resolver: AbstractResolver, i) =>
7979
if (i > 3) {
8080
assert(resolver.getName === s"repo-${i - 3}")
81-
assert(resolver.getRoot === expected(i - 4))
81+
assert(resolver.asInstanceOf[IBiblioResolver].getRoot === expected(i - 4))
8282
}
8383
}
8484
}
@@ -111,26 +111,32 @@ class SparkSubmitUtilsSuite extends FunSuite with BeforeAndAfterAll {
111111
}
112112

113113
test("search for artifact at local repositories") {
114-
val main = new MavenCoordinate("my.awesome.lib", "mylib", "0.1")
114+
val main = new MavenCoordinate("my.great.lib", "mylib", "0.1")
115+
val dep = "my.great.dep:mydep:0.5"
115116
// Local M2 repository
116-
IvyTestUtils.withRepository(main, None, Some(SparkSubmitUtils.m2Path)) { repo =>
117+
IvyTestUtils.withRepository(main, Some(dep), Some(SparkSubmitUtils.m2Path)) { repo =>
117118
val jarPath = SparkSubmitUtils.resolveMavenCoordinates(main.toString, None, None, true)
118119
assert(jarPath.indexOf("mylib") >= 0, "should find artifact")
120+
assert(jarPath.indexOf("mydep") >= 0, "should find dependency")
119121
}
120122
// Local Ivy Repository
121123
val settings = new IvySettings
122124
val ivyLocal = new File(settings.getDefaultIvyUserDir, "local" + File.separator)
123-
IvyTestUtils.withRepository(main, None, Some(ivyLocal), true) { repo =>
125+
IvyTestUtils.withRepository(main, Some(dep), Some(ivyLocal), true) { repo =>
124126
val jarPath = SparkSubmitUtils.resolveMavenCoordinates(main.toString, None, None, true)
125127
assert(jarPath.indexOf("mylib") >= 0, "should find artifact")
128+
assert(jarPath.indexOf("mydep") >= 0, "should find dependency")
126129
}
127130
// Local ivy repository with modified home
128131
val dummyIvyLocal = new File(tempIvyPath, "local" + File.separator)
129-
IvyTestUtils.withRepository(main, None, Some(dummyIvyLocal), true) { repo =>
132+
settings.setDefaultIvyUserDir(new File(tempIvyPath))
133+
IvyTestUtils.withRepository(main, Some(dep), Some(dummyIvyLocal), useIvyLayout = true,
134+
ivySettings = settings) { repo =>
130135
val jarPath = SparkSubmitUtils.resolveMavenCoordinates(main.toString, None,
131136
Some(tempIvyPath), true)
132137
assert(jarPath.indexOf("mylib") >= 0, "should find artifact")
133138
assert(jarPath.indexOf(tempIvyPath) >= 0, "should be in new ivy path")
139+
assert(jarPath.indexOf("mydep") >= 0, "should find dependency")
134140
}
135141
}
136142

0 commit comments

Comments
 (0)