aboutsummaryrefslogtreecommitdiff
path: root/stage2/DirectoryDependency.scala
blob: 9a50be4c94e0295c1f554fec779fe45cfb0f1438 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
package cbt
import java.io.File
/** You likely want to use the factory method in the BasicBuild class instead of this. */
object DirectoryDependency {
  def apply( path: File, subBuild: String )( implicit context: Context ): LazyDependency =
    apply( path, Some( subBuild ) )
  def apply( path: File, subBuild: Option[String] = None )( implicit context: Context ): LazyDependency =
    apply( context.copy( workingDirectory = path ), subBuild )

  def apply( context: Context, subBuild: Option[String] ): LazyDependency = {
    val lib: Lib = new Lib( context.logger )
    val d = context.workingDirectory
    val relativeBuildFile = lib.buildDirectoryName ++ File.separator ++ lib.buildFileName
    val buildDirectory = d / lib.buildDirectoryName
    val buildFile = buildDirectory / lib.buildFileName

    if ( !buildFile.isFile && ( buildDirectory / "Build.scala" ).isFile ) {
      throw new Exception(
        s"""expected $relativeBuildFile but found ${lib.buildDirectoryName ++ File.separator ++ "Build.scala"} in $d"""
      )
    }

    if ( buildDirectory.exists && buildDirectory.listOrFail.nonEmpty && !buildFile.exists ) {
      throw new Exception(
        s"No file ${lib.buildFileName} (lower case) found in " ++ buildDirectory.string
      )
    }

    def loadBuild: AnyRef = {
      val actualBuildFile = (
        buildFile.getParentFile.getCanonicalFile.getName ++ File.separator ++ buildFile.getCanonicalFile.getName
      )
      if ( actualBuildFile =!= relativeBuildFile ) {
        throw new Exception( s"expected $relativeBuildFile but found $actualBuildFile in " ++ context.workingDirectory.string )
      }

      if ( buildFile.isFile ) (
        loadCustomBuildWithDifferentCbtVersion
        getOrElse loadCustomBuild
      )
      else if ( d.getCanonicalFile.getName === lib.buildDirectoryName && ( d / lib.buildFileName ).exists )
        new cbt.ConcreteBuildBuild( context )
      else
        new BasicBuild( context )
    }

    def loadCustomBuildWithDifferentCbtVersion: Option[AnyRef] = {
      ( "cbt:" ++ GitDependency.GitUrl.regex ++ "#[a-z0-9A-Z]+" ).r
        .findFirstIn( buildFile.readAsString )
        .map( _.drop( 4 ).split( "#" ) )
        .flatMap {
          case Array( base, hash ) =>
            val sameCbtVersion = context.cbtHome.string.contains( hash )
            if ( sameCbtVersion ) None else Some {
              // Note: cbt can't use an old version of itself for building,
              // otherwise we'd have to recursively build all versions since
              // the beginning. Instead CBT always needs to build the pure Java
              // Launcher in the checkout with itself and then run it via reflection.
              val ( checkoutDirectory, dependency ) =
                GitDependency.withCheckoutDirectory( base, hash, Some( "nailgun_launcher" ) )(
                  context.copy( scalaVersion = None )
                )
              dependency
                .dependency
                .asInstanceOf[BaseBuild] // should work because nailgun_launcher/ has no cbt build of it's own
                .classLoader
                .loadClass( "cbt.NailgunLauncher" )
                .getMethod( "getBuild", classOf[AnyRef] )
                .invoke( null, context.copy( cbtHome = checkoutDirectory ) )
            }
        }
    }

    def loadCustomBuild: AnyRef = {
      lib.logger.composition( "Loading build at " ++ buildDirectory.string )
      val buildBuild = apply(
        context.copy( workingDirectory = buildDirectory, scalaVersion = None ), None
      ).dependency.asInstanceOf[BuildInterface]
      import buildBuild._
      val managedContext = context.copy( parentBuild = Some( buildBuild ) )

      val buildClasses =
        buildBuild.exportedClasspath.files.flatMap(
          lib.topLevelClasses( _, classLoader, false )
            .filter( _.getSimpleName === lib.buildClassName )
            .filter( classOf[BaseBuild] isAssignableFrom _ )
        )

      if ( buildClasses.size === 0 ) {
        throw new Exception(
          s"You need to define a class ${lib.buildClassName} extending an appropriate super class in\n"
            + buildFile ++ "\nbut none found."
        )
      } else if ( buildClasses.size > 1 ) {
        throw new Exception(
          s"You need to define exactly one class ${
            lib.buildClassName
          } extending an appropriate build super class, but multiple found in "
            + buildDirectory + ":\n" + buildClasses.mkString( "\n" )
        )
      } else {
        val buildClass = buildClasses.head
        buildClass.constructors.find( _.parameterTypes.toList === List( classOf[Context] ) ).map {
          _.newInstance( managedContext ).asInstanceOf[AnyRef]
        }.getOrElse {
          throw new Exception(
            s"Expected class ${lib.buildClassName}(val context: Context), but found different constructor in\n"
              + buildDirectory ++ "\n"
              + buildClass ++ "(" ++ buildClass.getConstructors.map( _.getParameterTypes.mkString( ", " ) ).mkString( "; " ) + ")"
          )
        }
      }
    }

    // wrapping this in lazy so builds are not loaded from disk immediately
    // this definitely has the advantages that cbt can detect cycles in the
    // dependencies, but might also positively affect performance
    new LazyDependency( {
      val build = lib.getReflective( loadBuild, subBuild )( context )
      try {
        build.asInstanceOf[Dependency]
      } catch {
        case e: ClassCastException =>
          throw new RuntimeException( "Your class " ++ lib.buildClassName ++ s" needs to extend class BaseBuild in $buildFile", e )
      }
    } )( context.logger, context.transientCache, context.classLoaderCache )
  }
}