aboutsummaryrefslogtreecommitdiff
path: root/stage2/DirectoryDependency.scala
blob: cfc0bfd1e019c9d8cc3f2e2e1081ee117404a151 (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
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 )
              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( buildDirectory, None )( context ).dependency.asInstanceOf[BuildInterface]
      import buildBuild._
      val managedContext = context.copy( parentBuild = Some( buildBuild ) )

      val buildClasses =
        buildBuild.exportedClasspath.files.flatMap(
          lib.iterateClasses( _, 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.getConstructors.find( _.getParameterTypes.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 ++ " needs to extend class BaseBuild in $buildFile", e )
      }
    } )( context.logger, context.transientCache, context.classLoaderCache )
  }
}