Schlagwort-Archive: Teamcity

Anwender durch zielgerichtete Release Notes in die Produktentwicklung integrieren

In diesem Video zeige ich eine von vielen maßgeschneiderten Funktionen unseres Release Prozesses. Das Ziel dabei war es die Anwender noch mehr in die Produktentwicklung einzubinden.

Die langfristige Vision: Anwender bringen selbst die Anforderungen ein. Wie das genau gemeint ist und wie wir das umgesetzt haben, seht ihr im Video. Unter anderem kommen YouTrack und TeamCity zum Einsatz.

 

Über ein Rake-Skript, welches auf ein bereits existierendes YouTrack-Gem zurückgreift, sprechen wir die Rest-API an. Vielen Dank an Alexander Groß von GROSSWEBER für die Hilfe bei der Umsetzung unserer Vision.

Wer weitere Einblicke in unseren Prozess bekommen möchte, der kann mir dazu einen Kommentar hinterlassen.

Build Script Interaktion mit TeamCity

Kürzlich standen wir vor der Aufgabe aus dem Build Skript, welches bei uns in Ruby geschrieben ist, Informationen an TeamCity, unseren Build Server, zu senden. Konkret wollten wir die interne Versionsnummer unseres Produkts in der Oberfläche anzeigen. Dazu muss lediglich im Skript eine Konsolenausgabe getätigt werden, die einem gewissen Format entspricht. In Ruby ist das der Befehl ‘puts’:

puts „##teamcity[buildNumber ‚#{build}‘]“

Dabei ist ‘build’ unsere interne Versionsnummer, die wir zuvor ermittelt haben. In einem Batch-Skript wäre dann der Echo-Befehl abzusetzen. Hier findet sich die passende Hilfe von JetBrains, welche weiteren Möglichkeiten es gibt.

Alexander Groß bietet hier ein fertiges Ruby Skript an, welches ihr bei euch einbinden könnt.

Aktuelle NuGet Packages in Build Skripten verwenden

Alex Groß von GROSSWEBER hat uns freundlicherweise eine Ruby Klasse geschrieben, welche es ermöglicht im Build Vorgang immer die aktuellste Version eines NuGet Package heranzuziehen.

Wer beispielsweise MSpec als UnitTesting Framework einsetzt und ein entsprechendes Skript in seinem Build Server verwendet, der hatte das Problem, dass das Skript angepasst werden musste, sobald man MSpec per NuGet aktualisierte. Das gilt natürlich für jegliches Package, auf welches in Skripten direkt referenziert wird.

Um die Problematik nun elegant zu lösen, sei exemplarisch unten folgendes Szenario aufgelistet:

 

Hier zeige ich den Ruby Code, welcher bei uns im Build Skript zur Ausführung der Unit Tests verwendet wird. In Zeile 3 wird die Lösung von Alex verwendet. Erster Parameter gibt den Namen des Package an, während zweiter Parameter besagt wo alle Packages im Repository liegen. Als Rückgabewert erhält man den Pfad zur aktuellsten Version. Im nächsten Schritt in Zeile 4 iteriere ich über unser Binaries Verzeichnis drüber. Dabei werden alle DLLs verarbeitet, die mit ‘comWORK’ beginnen. Als Ausnahme gebe ich alle Dateien an, die auf ‘resources’ enden. Das Ergebnis ist eine Liste von DLLs, welche MSpec dann prüft bzw. ausführt. Die Reports landen im Verzeichnis ‘Reports’ und liegen in Form von html Dateien zur Ansicht bereit.

   1: desc 'Unit Tests'

   2: task :unit do

   3:     mspec = NuGetLatest('Machine.Specifications', 'Source\_Solutions\packages')

   4:     FileList.new('Binaries/**/comWORK.*.dll').exclude('**/*.resources.dll').each do |f|

   5:         Mspec.run({

   6:             :tool => mspec + '/tools/mspec-clr4.exe',

   7:             :reportdirectory => 'Reports',

   8:             :assembly => f

   9:         })

  10:     end

  11: end

 

Hier das von Alex bereitgestellte Ruby File, welches immer das Verzeichnis der aktuellsten Package Version zurückliefert. Anpassungen sind keine nötig.

   1: class NuGetLatest

   2:   attr_reader :path

   3:  

   4:   def initialize(package_name, packages_dir = Dir.pwd)

   5:     @package_name = package_name

   6:  

   7:     raise "#{packages_dir} is not a directory" unless File.directory? packages_dir

   8:  

   9:     Dir.chdir packages_dir do

  10:       candidates = Dir["#{package_name}*"]

  11:       raise "No package '#{package_name}' was found in #{packages_dir}" unless candidates.any?

  12:  

  13:       latest = find_latest_version candidates

  14:       @path = File.join packages_dir, latest

  15:     end

  16:   end

  17:  

  18:   def to_s

  19:     @path

  20:   end

  21:  

  22:   def +(other)

  23:     to_s + other

  24:   end

  25:  

  26:   private

  27:   def find_latest_version(candidates)

  28:     return candidates.first if candidates.length == 1

  29:  

  30:     latest = candidates.map { |path|

  31:       version = path.sub /^#{@package_name}./, ''

  32:       version = parse_version version

  33:  

  34:       {

  35:         :version => version,

  36:         :path => path

  37:       }

  38:     }.max { |left, right| left[:version] <=> right[:version] }

  39:  

  40:     latest[:path]

  41:   end

  42:  

  43:   def parse_version(version_string)

  44:     major, minor, patch, revision, special = version_string.match(/^(\d+)\.(\d+)\.(\d+)\.?(\d+)?-?(.+)?$/).captures

  45:     SemVer.new(major.to_i, minor.to_i, patch.to_i, revision.to_i, special)

  46:   end

  47:  

  48:   # Borrowed from the SemVer gem and enhanced to support .NET's 4-digit versioning system.

  49:   class SemVer

  50:     attr_accessor :major, :minor, :patch, :revision, :special

  51:  

  52:     def initialize(major = 0, minor = 0, patch = 0, revision = 0, special = '')

  53:       major.kind_of? Integer or raise "invalid major: #{major}"

  54:       minor.kind_of? Integer or raise "invalid minor: #{minor}"

  55:       patch.kind_of? Integer or raise "invalid patch: #{patch}"

  56:       revision.kind_of? Integer or raise "invalid revision: #{revision}"

  57:  

  58:       unless special.nil? or special.empty?

  59:         special =~ /[A-Za-z][0-9A-Za-z-]+/ or raise "invalid special: #{special}"

  60:       end

  61:  

  62:       @major, @minor, @patch, @revision, @special = major, minor, patch, revision, special

  63:     end

  64:  

  65:     def <=>(other)

  66:       maj = @major.to_i <=> other.major.to_i

  67:       return maj unless maj == 0

  68:  

  69:       min = @minor.to_i <=> other.minor.to_i

  70:       return min unless min == 0

  71:  

  72:       pat = @patch.to_i <=> other.patch.to_i

  73:       return pat unless pat == 0

  74:  

  75:       rev = @revision.to_i <=> other.revision.to_i

  76:       return rev unless rev == 0

  77:  

  78:       spe = @special <=> other.special

  79:       return spe unless spe == 0

  80:  

  81:       0

  82:     end

  83:  

  84:     include Comparable

  85:   end

  86: end

  87:  

  88: module Conversions

  89:   def NuGetLatest(package_name, packages_dir)

  90:     NuGetLatest.new(package_name, packages_dir)

  91:   end

  92: end

  93:  

  94: include Conversions

 

Hier die MSpec Klasse zum Ausführen der Unit Tests. Vorsicht: Das Skript prüft eine Umgebungsvariable und ist dediziert auf uns zugeschnitten (Einsatz von Team City). Hier müsstet ihr also Anpassungen vornehmen.

//Update: Wie ich inzwischen erfahren habe, macht MSpec die Prüfung auf die von Team City gesetzte Umgebungsvariable inzwischen implizit!

   1: class Mspec

   2:   def self.run(attributes)

   3:     tool = attributes.fetch(:tool)

   4:     reportDirectory = attributes.fetch(:reportdirectory, '.').to_absolute

   5:     assembly = attributes.fetch(:assembly).to_absolute

   6:     

   7:     reportFile = assembly.name.ext('html').in(reportDirectory).to_absolute

   8:     FileUtils.mkdir_p reportFile.dirname

   9:     

  10:     mspec = tool.to_absolute

  11:     

  12:     Dir.chdir(assembly.dirname) do

  13:       sh "#{mspec.escape} #{'--teamcity ' if ENV['TEAMCITY_PROJECT_NAME']}--timeinfo --html #{reportFile.escape} #{assembly.escape}"

  14:     end

  15:   end

  16: end

Build Server Fehler mit licenses.licx

Ich hatte bereits früher über die Problematik gebloggt, dass Frameworks, welche Lizenzdaten beim kompilieren verarbeiten, auf Build Servern wie Team City Probleme machen können. DevExpress ist so ein Framework. Inzwischen haben wir in unserem Team eine Lösung gefunden: Die licenses.licx Dateien werden werden auf dem Build Server geleert (genauer: die vorhandenen Dateien werden durch leere ersetzt). Jetzt funktioniert alles problemlos, sodass Fehlermeldungen wie die unten der Vergangenheit angehören:

“My Project\licenses.licx(2): error LC0004: Exception occurred creating type ‚DevExpress.XtraTab.XtraTabControl, DevExpress.XtraEditors.v11.2, Version=11.2.5.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a‘ System.TypeInitializationException: Der Typeninitialisierer für "DevExpress.XtraTab.XtraTabControl" hat eine Ausnahme verursacht. —> System.Reflection.TargetInvocationException: Ein Aufrufziel hat einen Ausnahmefehler verursacht. —> System.IO.FileNotFoundException: Die Datei oder Assembly "EnvDTE, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" oder eine Abhängigkeit davon wurde nicht gefunden. Das System kann die angegebene Datei nicht finden.

Build Server Fehler mit licenses.licx

Wie viele andere sicherlich auch, verwenden wir ein Drittherstellerframework. Im vorliegenden Fall geht es um das kostenpflichtige DevExpress. Wir hatten kürzlich auf unserem Build Server mit Teamcity, auf welchem man DevExpress ebenfalls installieren muss (kurze Info: dafür benötigt man keine separate Lizenz), folgenden Compiler Fehler:

My Project\licenses.licx(2): error LC0004: Exception occurred creating type ‚DevExpress*‘ System.TypeInitializationException: Der Typeninitialisierer für "DevExpress.*" hat eine Ausnahme verursacht. —> System.Reflection.TargetInvocationException: Ein Aufrufziel hat einen Ausnahmefehler verursacht. —> System.IO.FileNotFoundException: Die Datei oder Assembly "EnvDTE, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" oder eine Abhängigkeit davon wurde nicht gefunden. Das System kann die angegebene Datei nicht finden. [*proj]

DevExpress* bedeutet, dass es sich um ein beliebiges Control handeln kann, welches in einem Projekt verwendet wird. Ein Fehler dieser Art kann theoretisch mit jedem beliebigen Framework, welches mit Lizenzen hantiert, auftreten. Bei DevExpress selbst habe ich einige Einträge dazu gefunden:

Es gibt im Netz dazu schon einige Einträge, unter anderem habe ich dazu einen recht aktuellen vom 25.10.11 gefunden, in welchem empfohlen wird, dass man diese Dateien aus dem Repository ausschließt. Weitere Links findet ihr am Ende.

Bei uns hat allerdings nur eine Lösung Wirkung gezeigt: Wir haben bei allen Entwicklern und auf allen Buildservern (inkl. aller Agents!) DevExpress entfernt und überall neu installiert. Da es in der Zwischenzeit ein Update gab, mussten wir mit dem DevExpress ProjectConverter alle Projekte aktualisieren lassen. Danach ging wieder alles. Ob die Neuinstallation schon ausgereicht hätte weiß ich nicht, da wir wie gesagt auch noch eine Konvertierung durchgeführt haben.

Restliche Quellen:

%d Bloggern gefällt das: