CreateProcess failed: The system cannot find the file specified.

Beim Bau der Bibliothek skia unter Windows bekam ich diese Fehlermeldung:

[419/2871] copy ../../third_party/externals/icu/common/icudtl.dat icudtl.dat
FAILED: icudtl.dat
python .../skia/build/skia/gn/cp.py ../../third_party/externals/icu/common/icudtl.dat icudtl.dat
CreateProcess failed: The system cannot find the file specified.
ninja: fatal: ReadFile: The handle is invalid.

Wie es sich herausgestellt hat, lag das Problem darin, dass ninja versucht hat, Python aufzurufen, Python aber nicht gefunden hat. Genauer gesagt, python.exe.

Um die python.exe aus den depot_tools aufrufen zu können, muss man vorher erstmal das Skript update_depot_tools.bat aufrufen (wenn man das vorher nicht schon getan hat). Danach sollte sich eine python.exe in dem Ordner <some\path>\depot_tools\win_tools-2_7_6_bin\python\bin befinden. Der Pfad kann sich in der Zukunft vermutlich ändern. Jedenfalls war das bei mir der Fall. Diesen Pfad muss man noch in der Umgebungsvariable PATH hinzufügen, z.b. in einem Batch-Skript mit

set "PATH=%PATH%;<some\path>\depot_tools\win_tools-2_7_6_bin\python\bin"

Danach sollte der Fehler verschwunden sein.

Anmerkungen zu if errorlevel 0

Ab und an sieht man im Internet Batch-Code, bei denen es um die Abfrage geht, ob der Errorlevel 0 oder ungleich 0 ist. Dabei sieht man Code-Zeilen, die in etwa so aussehen:

if errorlevel 0 (
...
) else (
...
)

Diese Abfrage ist aber in vielen Fällen falsch. Im Grunde bedeutet sie: Ist der Errorlevel 0 oder größer. D.h. diese Abfrage kommt fast gar nicht in den else-Teil, weil der Errorlevel meist 0 oder größer ist. Außer man hat ein Programm, welches negative Errorlevel zurückgibt.

Wenn man nun aber wissen möchte, ob der Errorlevel 1 oder größer ist, dann muss die Abfrage anders gestaltet werden:

if errorlevel 1 (
...
) else (
...
)

mkdirs() gibt false zurück

mkdirs() ist in Java eine Funktion, die einen Ordner samt aller Väterordner erstellt, wenn diese noch nicht existieren. Man kann dies mit dem Linuxkommando

mkdir -p /path/to/some/dir

vergleichen. Wie es sich herausgestellt hat, gibt diese Funktion ein false zurück, selbst wenn sie einen Ordner erfolgreich erstellt hat. Hier ein Java-Beispielcode:

import java.io.*;
public class MyFirstClass {
public static void main(String[] args) {

String folderName = "/home/jenkins/a/b";
try {

File folder = new File(folderName);
if(!folder.mkdirs()) {
System.out.println("Folders created!");
} else {
System.out.println("Failed to create folders!");
}

} catch (Exception e) {

System.err.println("Things went wrong: " + e.getMessage());
e.printStackTrace();

}
}
}

In diesem Programm wird ein Ordner namens /home/jenkins/a/b erstellt. Zu diesem Code habe ich noch ein Shell-Skript geschrieben, dass mir bei jedem Aufruf den Ordner /home/jenkins/a löscht, den Code kompiliert und das Programm startet (build.sh):

 !/bin/sh
if [ -d "/home/jenkins/a" ]; then
rm -rvf /home/jenkins/a
fi
rm -vf *.class
javac MyFirstClass.java
if [ -f "MyFirstClass.class" ]; then
java MyFirstClass
fi

Bei jedem Aufruf dieses Skripts wurde am Ende der Ordner /home/jenkins/a/b erstellt, jedoch kam bei jedem Durchlauf die Meldung Failed to create folders!.

Hintergrund ist der: Die Funktion mkdirs() gibt nur dann ein true zurück, wenn alle Ordner in der Ordnerstruktur neu erstellt werden. Wenn Teile davon schon vorher existiert haben, dann gibt die Funktion ein false zurück. Dazu die Doku:

Returns:
true if and only if the directory was created, along with all necessary parent directories; false otherwise

https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/File.html#mkdirs()

Ähnliches findet sich auch in einem Bugreport für OpenJDK von vor ziemlich langer Zeit:

File.mkdir() will return false if the target exists. File.mkdirs() will return false if any directory in the path was not created (e.g., it already existed) which will usually be the case. But a programmer has the facilities to see if a directory exists (File.exists()) which yes, probably will be of more interest in most cases.

https://bugs.openjdk.java.net/browse/JDK-1241581

“File not found” beim dir Kommando

Mittels des Kommandos

dir /b "<Pfad>" 

kann man sich die Dateinamen in dem Ordner <Pfad> auflisten lassen. Also nur die Dateinamen, ohne den gesamten Pfadnamen und ohne sonstige Informationen. Nun war die Aufgabe nicht alle Dateinamen aufzulisten, sondern nur jene die eine bestimmte Endung hatten. Versucht habe ich dann erst einmal so:

dir /b "<Pfad>" *.cmd

Ich bekam eine Ausgabe, die mich etwas stutzig gemacht hat:

Woher kam die Meldung File not Found? Wie es sich herausgestellt hat, führt diese Art dir aufzurufen zu folgendem Verhalten: Zuerst werden alle Dateien in dem Ordner <Pfad> aufgelistet und dann alle *.cmd Dateien, die sich in dem Ordner befinden in dem ich mich gerade in der Konsole befinde. Und in diesem Beispiel war das eben der C:\ Ordner. Und weil ich in C:\ keine *.cmd Dateien hatte, hat dir mir die Fehlermeldung File not Found ausgegeben.

Um trotzdem alle Dateinamen mit bestimmter Endung in einem Ordner heraus zu finden, muss man das Kommando nur leicht abändern:

dir /b <Pfad> | findstr /R /C:"\.cmd$"

Mit findstr das letzte Zeichen prüfen

Mit dem Kommandowerkzeug findstr kann man unter Windows auf das letzte Zeichen eines Strings überprüfen. Eine Art dies zu bewerkstelligen kann man an folgendem Code sehen. Hier wird geprüft, ob die Variable %Project% mit einem Schrägstrich / endet oder nicht. Dabei wird ein Regex verwendet. Das Dollarzeichen in dem Regex steht hier für das Zeilenende, daher auch der “/$”:

@ECHO OFF
SETLOCAL EnableDelayedExpansion
SET "Project=/Folder1"
ECHO %Project%| findstr /R /C:"/$" 1>nul
echo %ERRORLEVEL%

SET "Project=/Folder1/"
ECHO %Project%| findstr /R /C:"/$" 1>nul
echo %ERRORLEVEL%

Bei diesem Code sollte man beachten, dass es kein Leerzeichen zwischen der Variablen %Project% und dem Pipelinezeichen | gibt. Gibt es ein Leerzeichen wird der gesamte Befehl missinterpretiert als würde das letzte Zeichen des Strings ein Leerzeichen sein und findstr gibt dann in beiden Fällen eine 1 zurück.

Der Jenkinsslave und die Javaversion

Wie sich mal wieder herausgestellt hat, ist die Javaversion auf einer Windows-Slave-Maschine von großer Bedeutung. Auf einer Windows-Slave-Maschine wollte ich eine Batch-Datei ausführen, die wiederum ein Powershell-Skript aufgerufen hat. Während des Durchlaufs bekam ich jedoch diese Fehlermeldung in Jenkins:

Import-Module : Could not load file or assembly 'file:///C:\Program Files\WindowsPowerShell\Modules\Msonline\1.1.183.17\Microsoft.Online.Administration.Automation.PSModule.dll' or one of its dependencies. An attempt was made to load a program with an incorrect format.

Das seltsame daran war jedoch, dass dieses Skript in der Eingabeaufforderung von Windows auf derselben Maschine ohne Probleme lief. Mit Jenkins bekam ich jedoch diese Fehlermeldung. Wie sich herausgestellt hat, war auf der Slave-Maschine eine x86-Version von Java installiert. Diese habe ich dann deinstalliert und die entsprechende x64-Version installiert. Danach musste man nur noch den Slave-Agenten neu starten und die Fehlermeldung war beim nächsten Bau verschwunden.

Quellen:

findstr Beispiel: Bestimmte Zeilen ausschließen

Manchmal muss man mit Hilfe des Tools findstr bestimmte Zeilen ausschließen aus einer langen Liste von Daten. Dies will ich hier an einem kleinen Beispiel zeigen. Angenommen, man hat eine Liste an Zeilen wie diese hier:

(TR3919) Power '1' has been deployed to version '1'
(TR9102) Power '2' has NOT been deployed to version '2'
Random text and 13773r5, with a big error…
(TR1337) complicated
(TR1239) error

Und nun möchte man alle Zeilen bekommen, die entweder mit (TRXXXX) anfangen (wobei XXXX eine Zahl zwischen 0000 und 9999 sein kann) oder die ein error enthalten. Zeilen, die ein has been deployed enthalten, sollten rausgefiltert werden. Das kann man mit folgendem Befehl erreichen:

findstr "(TR[0-9][0-9][0-9][0-9]) error" "data1.txt" | findstr /V /C:"has been deployed"

Der erste findstr-Befehl sucht alle Zeilen heraus, die ein (TRXXXX) oder ein error enthalten. Nun muss man noch die Zeilen herausfiltern, die ein has been deployed enthalten und das erreicht man mit Hilfe des zweiten findstr-Befehls.

Der Parameter /V dient dazu, all jene Zeilen zu finden, die den angegeben String nicht enthalten. Der Parameter /C gibt an, dass nach genau diesem String, also Zeichen für Zeichen genau, gesucht wird. Bzw. in diesem Fall soll also genau dieser String nicht dabei sein (eben wegen dem Parameter /V).

docker: Are you trying to mount a directory onto a file (or vice-versa)?

Vor kurzem bekam ich beim Versuch, eine Datei von meinem Windows-Host-System in einen Docker Container über dem -v Parameter zu mounten, diese Fehlermeldung:

docker: Error response from daemon: oci runtime error: container_linux.go:262: starting container process caused : … : Are you trying to mount a directory onto a file (or vice-versa)? Check if the specified host path exists and is the expected type.

Mein ursprüngliches Kommando lautete:

docker run -it -v /C/some/path/file.conf:/target/path/file.conf <image_name>

Die Lösung für dieses Problem ist folgende:

  • Erstmal den Container löschen, den ich versucht hatte zu starten: docker rm -f
  • Dockereinstellungen starten (in diesem Fall mit der rechten Maustaste über die Symbolleiste)
  • In dem Fenster mit den Dockereinstellungen auf den Reiter “Shared Drives” wechseln
  • In dem Fenster auf “Reset credentials” drücken
  • Die entsprechende Festplatte wieder aktivieren (in diesem Fall C:)
  • Auf “Apply” drücken und das Fenter schließen
  • Den Dockerdienst neu starten
  • Den docker-Befehl nochmals ausführen.

Quellen

https://stackoverflow.com/questions/45972812/are-you-trying-to-mount-a-directory-onto-a-file-or-vice-versa

Storage Accounts mit Azure CLI und Azure PowerShell auflisten

In diesem Post möchte ich nur kurz auflisten, wie man Storage Accounts in Azure mit Azure CLI und Azure PowerShell auflisten kann. Dies soll mehr für mich als Erinnerung dienen.

Azure CLI

az login
az storage account list

Azure PowerShell

Import-Module AzureRM

Login-AzureRmAccount
Get-AzureRmStorageAccount