// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. /** * This file contains functionality for getting dart version numbers using * our standard version construction method. Systems that does not include this * file should emulate the structure for revision numbers that we have here. * * The version number of a dart build is constructed as follows: * 1. The major, minor, build and patch numbers are extracted from the VERSION * file in the root directory. We call these MAJOR, MINOR, BUILD and PATCH. * 2. The svn revision number for the current checkout is extracted from the * source control system that is used in the current checkout. We call this * REVISION. * 3. If this is _not_ a official build, i.e., this is not build by our * buildbot infrastructure, we extract the user-name of the logged in * person from the operating system. We call this USERNAME. * 4. The version number is constructed as follows: * MAJOR.MINOR.BUILD.PATCH_rREVISION_USERNAME */ library version; import "dart:async"; import "dart:io"; /** * Generates version information for builds. */ class Version { String _versionFileName; String USERNAME; int REVISION; int MAJOR; int MINOR; int BUILD; int PATCH; Version(Path versionFile) { _versionFileName = versionFile.toNativePath(); } /** * Get the version number for this specific build using the version info * from the VERSION file in the root directory and the revision info * from the source control system of the current checkout. */ Future getVersion() { File f = new File(_versionFileName); Completer c = new Completer(); var wasCompletedWithError = false; completeError(String msg) { if (!wasCompletedWithError) { c.completeError(msg); wasCompletedWithError = true; } } f.exists().then((existed) { if (!existed) { completeError("No VERSION file"); return; } StringInputStream input = new StringInputStream(f.openInputStream()); input.onLine = () { var line = input.readLine().trim(); if (line == null) { completeError( "VERSION input file seems to be in the wrong format"); return; } var values = line.split(" "); if (values.length != 2) { completeError( "VERSION input file seems to be in the wrong format"); return; } var number = 0; try { number = int.parse(values[1]); } catch (e) { completeError("Can't parse version numbers, not an int"); return; } switch (values[0]) { case "MAJOR": MAJOR = number; break; case "MINOR": MINOR = number; break; case "BUILD": BUILD = number; break; case "PATCH": PATCH = number; break; default: completeError("Wrong format in VERSION file, line does not " "contain one of {MAJOR, MINOR, BUILD, PATCH}"); return; } }; input.onClosed = () { // Only complete if we did not already complete with a failure. if (!wasCompletedWithError) { getRevision().then((revision) { REVISION = revision; USERNAME = getUserName(); var userNameString = ""; if (USERNAME != '') userNameString = "_$USERNAME"; var revisionString = ""; if (revision != 0) revisionString = "_r$revision"; c.complete( "$MAJOR.$MINOR.$BUILD.$PATCH$revisionString$userNameString"); return; }); } }; }); return c.future; } String getExecutableSuffix() { if (Platform.operatingSystem == 'windows') { return '.bat'; } return ''; } int getRevisionFromSvnInfo(String info) { if (info == null || info == '') return 0; var lines = info.split("\n"); RegExp exp = new RegExp(r"Last Changed Rev: (\d*)"); for (var line in lines) { if (exp.hasMatch(line)) { String revisionString = (exp.firstMatch(line).group(1)); try { return int.parse(revisionString); } catch(e) { return 0; } } } return 0; } Future getRevision() { if (repositoryType == RepositoryType.UNKNOWN) { return new Future.immediate(0); } var isSvn = repositoryType == RepositoryType.SVN; var command = isSvn ? "svn" : "git"; command = "$command${getExecutableSuffix()}"; var arguments = isSvn ? ["info"] : ["svn", "info"]; ProcessOptions options = new ProcessOptions(); // Run the command from the root to get the last changed revision for this // "branch". Since we have both trunk and bleeding edge in the same // repository and since we always build TOT we need this to get the // right version number. Path toolsDirectory = new Path(_versionFileName).directoryPath; Path root = toolsDirectory.join(new Path("..")); options.workingDirectory = root.toNativePath(); return Process.run(command, arguments, options).then((result) { if (result.exitCode != 0) { return 0; } return getRevisionFromSvnInfo(result.stdout); }); } bool isProductionBuild(String username) { return username == "chrome-bot"; } String getUserName() { // TODO(ricow): Don't add this on the buildbot. var key = "USER"; if (Platform.operatingSystem == 'windows') { key = "USERNAME"; } if (!Platform.environment.containsKey(key)) return ""; var username = Platform.environment[key]; // If this is a production build, i.e., this is something we are shipping, // don't suffix the version with the username. if (isProductionBuild(username)) return ""; return username; } bool isGitRepository() { var currentPath = new Path(new Directory.current().path); while (!new Directory.fromPath(currentPath.append(".git")).existsSync()) { currentPath = currentPath.directoryPath; if (currentPath.toString() == "/") { break; } } return new Directory.fromPath(currentPath.append(".git")).existsSync(); } RepositoryType get repositoryType { if (new Directory(".svn").existsSync()) return RepositoryType.SVN; if (isGitRepository()) return RepositoryType.GIT; return RepositoryType.UNKNOWN; } } class RepositoryType { static final RepositoryType SVN = const RepositoryType("SVN"); static final RepositoryType GIT = const RepositoryType("GIT"); static final RepositoryType UNKNOWN = const RepositoryType("UNKNOWN"); const RepositoryType(String this.name); static RepositoryType guessType() { if (new Directory(".svn").existsSync()) return RepositoryType.SVN; if (new Directory(".git").existsSync()) return RepositoryType.GIT; return RepositoryType.UNKNOWN; } String toString() => name; final String name; }