Caching dei risultati di compilazione

0

Questa è una domanda "identificazione tecnologia / concetto".

Ho un progetto piuttosto grande in un repository DCVS.

I tempi di costruzione sono mooolto (circa un'ora) da una pulizia completa.

Il progetto è già suddiviso in sottoprogetti, con costruzione incrementale. Ma ci vuole ancora molto tempo quando si cambiano i rami, ecc., Poiché spesso dovrò compilare molto.

Avevo un'idea di mettere in cache gli output del sottoprogetto (sto usando Scala, quindi in questo caso è un gruppo di file .class) e li pubblichiamo con l'hash del codice sorgente come chiave. Prima di una compilazione, scaricavo qualsiasi output corrispondente agli hash di uno dei sottoprogetti. Il "dolore" della costruzione sarebbe condiviso e riutilizzato in tutta l'organizzazione, incluso il server CI.

Non posso essere stato il primo a pensarci.

Esiste un nome per questo caching-by-source (piuttosto che una versione esplicita)? È comunemente implementato? Qualche strumento esistente intorno a questo?

EDIT: ho trovato un esempio in scons , un strumento utilizzato principalmente per le build C / C ++.

    
posta Paul Draper 08.12.2014 - 20:45
fonte

1 risposta

1

SBT

Lo strumento di generazione standard per scala è sbt (Scala Build Tool). All'interno di sbt ci sono tre modi per fare le dipendenze :

  • Dichiarazioni nel tuo progetto
  • File Maven POM
  • Configurazione Ivy

Per utilizzare Maven, è necessario specificare i resolver per la posizione del repository:

resolvers += "Local Maven Repository" at "file://"+Path.userHome.absolutePath+"/.m2/repository"
resolvers += "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots"

Potresti anche sovrascriverlo creando un file di configurazione locale: ~/.sbt/repositories

[repositories]
local
my-maven-repo: https://example.org/repo
my-ivy-repo: https://example.org/ivy-repo/, [organization]/[module]/[revision]/[type]s/[artifact](-[classifier]).[ext]

Se dovessi veramente , potresti anche specificare un URL e impostare in qualche modo l'hosting locale su un server che controlli. Si noti che questa non è una soluzione ideale

libraryDependencies += "slinky" % "slinky" % "2.1" from "https://slinky2.googlecode.com/svn/artifacts/2.1/slinky.jar"

Tuttavia, questa è solo metà della battaglia. Certo, puoi estrarre i vasi da qualche posto, ma devi anche metterli lì . Se non stai mettendo i moduli là fuori per qualcun altro da scaricare e usare, dovranno ricompilarli.

Potresti configurare un server Sonotype nexus e quindi pubblicare su di esso:

publishTo := Some("Sonatype Snapshots Nexus" at "https://oss.sonatype.org/content/repositories/snapshots")

Oppure potresti avere un repository comunemente montato (non penso che questa sarebbe una soluzione migliore ):

publishTo := Some(Resolver.file("file",  new File( "path/to/my/maven-repo/releases" )) )

Puoi configurarlo per pubblicare alcune cose qui e alcune cose lì:

publishTo := {
  val nexus = "https://oss.sonatype.org/"
  if (isSnapshot.value)
    Some("snapshots" at nexus + "content/repositories/snapshots") 
  else
    Some("releases"  at nexus + "service/local/staging/deploy/maven2")
}

Ma la cosa è che devi pubblicarlo per permettere ad altre persone di scaricarlo.

Maven

Maven è (o dovrebbe essere) ben noto agli sviluppatori Java. Non è qualcosa che è comunemente usato con scala. Ma è possibile per far sì che scala e maven lavorino insieme . Insieme a src/main/java avrai src/main/scala . Avrai anche un plugin nel tuo .pom:

<plugin>
  <groupId>net.alchim31.maven</groupId>
  <artifactId>scala-maven-plugin</artifactId>
  <version>3.1.3</version>
  <executions>
    <execution>
      <goals>
        <goal>compile</goal>
        <goal>testCompile</goal>
      </goals>
    </execution>
  </executions>
</plugin>

A questo punto, stai usando Maven e stai distribuendo le risorse Maven sul server Sonotype nexus.

cose generiche

Tutto questo, anche se entrambi gli approcci dipendono da qualcos'altro. È necessario modulare il progetto in modo che i piccoli vasi possano essere compilati, compilati e distribuiti sul server degli artefatti.

Se il tuo progetto non è configurato in questo modo, dovrai cambiarlo in modo che le piccole parti indipendenti siano distinte.

Potresti scoprire che la cosa più semplice da fare (quindi non devi ricordarti di fare una pubblicazione o una distribuzione) è configurare un server di integrazione continua che preleva le modifiche dal tuo server di controllo delle versioni, costruendole e distribuire automaticamente build di successo sul server.

Ciò implica anche un certo grado di disciplina con il controllo delle versioni degli artefatti. Potresti voler dare un'occhiata a versioning semantico . L'identificativo per le informazioni di rilascio in modo che non si calpestino l'un l'altro e non distribuisca istantanee in competizione o sovrascriva il numero di versione di qualcun altro. Ti troverai con versioni come 1.0.3-pauld.0.1 in modo che siano namespace.

E così, specificherete con sbt o maven che questo progetto usa alcuni artefatti in alcune versioni. Il numero della versione preliminare probabilmente contiene il nome del ramo o dell'autore. Sarà distribuito su un server in cui è quindi possibile estrarlo. Quando qualcun altro passa a quel ramo, quei moduli saranno disponibili per lo strumento di compilazione da scaricare e utilizzare senza doverli ricompilare.

lingue e strumenti

Una cosa fondamentale da ricordare qui è che non si distribuiscono file .class. Invece, stai dispiegando .jars. Tutto è costruito attorno a una classe. Compilata localmente o alle librerie scaricate. Mentre si potrebbe pensare di farlo con altri linguaggi, con Java e Scala gli IDE si infastidirebbero molto se i file .class ei file .java o .scala diventassero fuori sincrono. Se stai utilizzando vim e javac o scalac , potresti trovare altri strumenti utili per le risorse compilate singolarmente.

Tuttavia, sottolineerò che non è così banale come prima apparirebbe. Troverai cose come Outer$Inner.class e Outer$1$3.class nelle istanze in cui hai classi interne (il secondo esempio è il file compilato per la terza classe interna anonima nella prima classe interna anonima nella classe Esterno). Con le classi interne anonime, questa è dipendente dalla posizione nel file: aggiungi del codice e Outer$1.class diventa Outer$2.class . Ciò rende la cache per fonte piuttosto problematica e potrebbe non funzionare affatto con molte lingue derivate dalla JVM. Per fare un esempio:

object App {

    def isEven(i: Int) = i % 2 == 0

    def isOdd(i: Int) = i % 2 == 1

    def main(args: Array[String]): Unit = {
        val n = (1 to 10).toList
        n.filter(isEven).flatMap(i => n.filter(isOdd).map(j => i * j))
    }

}

una volta compilato con scalac App.scala ottieni:

App$$anonfun$main$1.class
App$$anonfun$main$2$$anonfun$apply$1.class
App$$anonfun$main$2$$anonfun$apply$2.class
App$$anonfun$main$2.class
App$.class
App.class

E puoi vedere il problema dell'ordine della classe che le cose nel codice possono facilmente cambiare. Ciò renderebbe gli strumenti come scons molto meno utili e forse anche problematici.

    
risposta data 10.12.2014 - 01:54
fonte

Leggi altre domande sui tag