Recently I had the opportunity to create an automated Build script for all our software. Being that “all our software” was over 50 applications and over a dozen libraries, that was no straightforward task.
Because all but 2 of our programs were written in .NET, MSBuild was pretty much a shoe-in for the task. And while getting all the information organized properly (dependencies, project file locations, etc) was a lot of gruntwork, I was able to get all the C# Applications building.
This did leave some outliers- those 2 pieces of software that use Java. Specifically, a Java program that effectively acts as an adapter between C# and Java, and some Jasper Reports.
My goal was to have the build script, and the files it needs, directly in source control. For MSBuild itself, I was able to have a batch file try to find the appropriate Visual Studio Tools location by simply checking for the existence of files from newest versions to oldest versions. For Java, I settled on the use of the build.xml that is created by and maintained by Netbeans, which we’ve settled on using for the Java projects we maintain. This still left us with a few considerations.
Wherefore art thou, JDK?
The first issue was that Ant requried a JDK to build the Java project, which is reasonable. What wouldn’t be reasonable, however, would be slapping the 130MB+ JDK files directly into the SVN. And since my goal was a “configure-free” usage, it seemed like I needed to come up with a way to find an appropriate JDK version.
My “solution” was a bit of a hack; as this was all running from a batch file, I created a C# console program that effectively searched through the two system Program Files folders to try to find the latest version of the Java JDK to use.
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 |
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; namespace FindJDK { //Java JDK is freaking annoying. We can either use JAVA_HOME and assume it was set AND that it was set to a JDK, or try to find one ourselves. // This could probably be done in batch but I'd rather not go insane trying to deal with NT Extensions and debugging that, so I'll stick with the language I'm familiar with. //Builds as a 32-bit program and tries to find 32-bit JDK Installations. It will try to find the newest one and will save the path as a batch file- the file to save should be provided on the command-line. class JavaVersion : IComparable<javaversion> { public int Major; public int Minor; public JavaVersion(int pMajor,int pMinor) { Major = pMajor; Minor = pMinor; } public int CompareTo(JavaVersion other) { if(Major.CompareTo(other.Major)==0) { return Minor.CompareTo(other.Minor); } else { return Major.CompareTo(other.Major); } } } class Program { static int Main(string[] args) { Console.WriteLine("JDK Installation Locator " + Assembly.GetExecutingAssembly().GetName().Version.ToString()); String sTargetFile = "jdkvar.bat"; //ensure a target path file was given. if(args.Length>0) { sTargetFile = args[0]; } //now find the JDK install. String[] sProgramFiles = new string[]{Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86)}; String sFoundVersion=null; foreach(String searchpath in sProgramFiles) { if(!String.IsNullOrEmpty(searchpath)) { //JDK will be in a Java folder. is there a Java Folder here? String sJavaPath = Path.Combine(searchpath, "java"); if(Directory.Exists(sJavaPath)) { //jdk1.7.0_67 //yep, alright- find each folder that starts with "jdk". DirectoryInfo javadir = new DirectoryInfo(sJavaPath); Dictionary<JavaVersion, DirectoryInfo> JavaDirs = new Dictionary<JavaVersion, DirectoryInfo>(); DirectoryInfo[] JDKFolders = (from j in javadir.GetDirectories() where j.Name.StartsWith("JDK", StringComparison.OrdinalIgnoreCase) select j).ToArray(); foreach(DirectoryInfo checkdir in JDKFolders) { String testname = checkdir.Name; int Major, Minor; String MajorStr="", MinorStr=""; if (testname.Contains("_")) MinorStr = testname.Substring(testname.IndexOf("_") + 1); else MinorStr = "0"; String[] splitname = testname.Split('.'); if (splitname.Length > 2) MajorStr = splitname[1]; if(!String.IsNullOrEmpty(MajorStr) && !String.IsNullOrEmpty(MinorStr)) { if(int.TryParse(MajorStr,out Major) && int.TryParse(MinorStr,out Minor)) { JavaDirs.Add(new JavaVersion(Major,Minor),checkdir); } } } if(JavaDirs.Count > 0) { KeyValuePair<JavaVersion,DirectoryInfo> latestversion = (from p in JavaDirs orderby p.Key descending select p).First(); sFoundVersion = latestversion.Value.FullName; break; } } } } if(sFoundVersion!=null) { Console.WriteLine("JDK Found- using installation at " + sFoundVersion); using(StreamWriter sw = new StreamWriter(new FileStream(sTargetFile,FileMode.Create))) { sw.WriteLine("SET JDKPATH=" + sFoundVersion + "\\bin\nSET PATH=%PATH%;%JDKPATH%"); } return 0; } else { Console.WriteLine("JDK Installation could not be found. Aborting."); return 1; } } } }</javaversion> |
Probably not a perfect solution to the problem, but it got the job done for what I was after. Effectively, it searches for a JDK install by looking for the appropriate folders, and then trying to sort them based on parsing the directory names for the Java install and grabbing the most recent one.
With that problem out of the way, I ran the build script- and found that while Ant claimed a successful build, the output directory only had the jar for our program- that is, the dist folder did not also have a lib folder. This was puzzling as building from Netbeans created that folder. As it turned out, this was because the functionality to copy the libraries was a “add-on” file. I was able to find this add-on file in my Netbeans install folder, within C:\Program Files (x86)\NetBeans 8.0\java\ant\extra\org-netbeans-modules-java-j2seproject-copylibstask.jar, and copied it into mt local folder. Than I invoked the ant task with:
1 |
call "..\Build Script\apache-ant-1.9.4\bin\ant" -Dlibs.CopyLibs.classpath="%buildfolder%\org-netbeans-modules-java-j2seproject-copylibstask.jar" -Dnb.internal.action.name=rebuild clean jar |
technically, you cannot build Java with MSBuild. however, you can use an exec node to run a batch file which can run ant and build Java, as we see here. Cross-language build scripting is usually going to involve something like that, particularly if those languages cross an eco-system. Visual Basic and C# or F# is no problem; but if you have Java and C# or Scala and Rust or any other set of combinations, you’ll find that the tooling is not going to be generic enough because those different software ecosystems have different goals and different designs which often don’t lend themselves well to building the other language, so you need to improvise.
Have something to say about this post? Comment!