diff --git a/tools/catalog.js b/tools/catalog.js index 9244adf9dd..3041e04609 100644 --- a/tools/catalog.js +++ b/tools/catalog.js @@ -217,6 +217,18 @@ _.extend(OfficialCatalog.prototype, { } }, + // Find a release that uses the given version of a tool. See: publish-for-arch + // in command-packages for more explanation. Returns information about a + // particular release version, or null if such release version does not exist. + getReleaseWithTool: function (toolSpec) { + var self = this; + buildmessage.assertInCapture(); + self._requireInitialized(); + return self._recordOrRefresh(function () { + return _.findWhere(self.releaseVersions, { tool: toolSpec }); + }); + }, + // Returns general (non-version-specific) information about a // release track, or null if there is no such release track. getReleaseTrack: function (name) { diff --git a/tools/commands-packages.js b/tools/commands-packages.js index a61454fc87..0538fe4664 100644 --- a/tools/commands-packages.js +++ b/tools/commands-packages.js @@ -376,6 +376,116 @@ main.registerCommand({ if (buildmessage.jobHasMessages()) return; + var versionLock = packageSource.dependencyVersions; + //If we don't have a valid version lock file, that's weird! Maybe we are a + //core package, which don't have version files. Anyway, we should not use + //publish-for-arch in this case. + if (!versionLock || !versionLock.toolVersion) { + process.stderr.write( +"This package has no valid version lock file: are you trying to use publish-for-arch on\n" + +"a core package? Publish-for-arch cannot guarantee safety. Please use\n" + +"publish --existing-version instead.\n"); + process.exit(1); + } + + // Let's separate out the tool, if we can. If we can't, thats super bad, but + // hopefully will not happen. + var oldTool = versionLock.toolVersion.split('@'); + if (oldTool.length !== 2) { + process.stderr.write( +"The version lock file on this package specifies an invalid meteor tool. That's weird.\n" + +"Publish-for-arch cannot guarantee safety with a corrupted version lock file! You can use\n" + +"publish --existing-version to try to get around this?\n"); + process.exit(1); + } + + var toolPackage = oldTool[0]; + var toolVersion = oldTool[1]; + if (toolVersion === "CHECKOUT" && + !files.inCheckout()) { + process.stderr.write( +"This package was published from a checkout of meteor! The tool cannot replicate\n" + +"that environment and will not even try. Please checkout meteor at the \n" + +"corresponding git commit and try again.\n"); + process.exit(1); + } + + if (toolVersion !== "CHECKOUT") { + if (files.inCheckout()) { + // The code running here, is probably not what you think it is. You + // might think that you are running from checkout, but we are going to + // springboard into a built release that is not running the code that + // you just wrote. That's super confusing, so we are not going to do + // that. If you ever find yourself doing this... well, you are running + // from checkout, so you can figure it out. + process.stderr.write( + "This package was published from a built version of meteor," + + "but you are running from checkout!\nConsider running from a " + + "proper Meteor release, so we can springboard correctly.\n"); + // process.stderr.exit(1); + var sufficientlyReasonableReleaseVersion = + catalog.official.getReleaseWithTool(versionLock.toolVersion); + throw new + main.SpringboardToSpecificRelease( + sufficientlyReasonableReleaseVersion, + "Errors while trying to publish for arch"); + + } + var currentToolPackage = release.current.getToolsPackage(); + var currentToolVersion = release.current.getToolsVersion(); + if (currentToolPackage !== toolPackage || + currentToolVersion !== toolVersion) { + // XXX: OK. Here is the story. + // + // Meteor does not have a concept of not running from release. That is, + // it runs from a release, or from checkout, not from a stand-alone + // tool. We don't record the release that we publish with in + // publish-for-arch, because that doesn't make sense. However, we can't + // just springboard to a tool, because, for now, in 0.9.3, we really + // want this to work on packages published pre-0.9.3. Just putting in + // springboarding to tool code is not going to work, because older + // versions of Meteor will just try to spingboard anyway. + // + // This is kind of a transitional hack. Going forward, there are several + // ways to fix this -- we could introduct some sort of local records (so + // we could create a temporary release record and run meteor from + // there), or we can teach meteor to just run from a tool, instead of a + // release. I like the latter better from a conceptual standpoint (why + // should we run from a release only?) but it doesn't have a lot of use + // cases. Alternatively, we can learn to simulate a release for older + // versions, and not for newer versions, or something. This will be + // worth thinking about when we have more information on how the system + // is set up and used. + // + // Now, a proof of correctness -- this relies on several things: + // + // 1. We only use the tool in order to publish. Other release + // information is irrelevant. (If that's ever false, we should write the + // release instead of the tool and save us the trouble) + // + // 2. Springboarding to a specific release will run the tool from that + // release, and not end up springboarding us to a different + // release. Even if there are patches for this release (or whatever), we + // are going to run the tool version of the release that we select here. + // + // 3. The only way to run a tool currently is from a release -- + // otherwise, we wouldn't need this explanation. (There is no way to + // remove a release from existence.) Ergo, there must be a release that + // contains a given tool, that we first used to publish this package. + // + // From 1 & 2, we get the idea that any release with the valid tool + // version will do. From 3, we know that such a release exists. + // + // XXX Once again, this is a hack. Various things could happen to change + // the above-mentioned points. When they do happen, in the not-so-near + // future, we will have more information on how to actually solve this + // problem. + var sufficientlyReasonableReleaseVersion = + catalog.official.getReleaseWithTool(versionLock.toolVersion); + throw new + main.SpringboardToSpecificRelease(sufficientlyReasonableReleaseVersion); + } + } // Now compile it! Once again, everything should compile, and if // it doesn't we should fail. Hopefully, of course, we have diff --git a/tools/main.js b/tools/main.js index 0b7ec77247..6e4c7beba4 100644 --- a/tools/main.js +++ b/tools/main.js @@ -95,6 +95,13 @@ main.SpringboardToLatestRelease = function (track) { this.track = track; }; +// Exception to throw from a command to exit, restart, and reinvoke +// the command with the given Meteor release. +main.SpringboardToSpecificRelease = function (releaseRecord, msg) { + this.releaseRecord = releaseRecord; + this.msg = msg; +}; + // Register a command-line command. // // options: @@ -270,7 +277,7 @@ var longHelp = exports.longHelp = function (commandName) { // not appear in the top-level help. If we one day want to make // these kinds of commands visible to casual users, we'll need a // way to mark them as visible or hidden. - + // Also, use helpDict to only include commands that have help text, // otherwise there is nothing to display if (n instanceof Command && ! n.hidden && helpDict[fullName]) @@ -316,7 +323,7 @@ var longHelp = exports.longHelp = function (commandName) { // Exit and restart the program, with the same arguments, but using a // different version of the tool and/or forcing a particular release. // -// - toolsVersion: required. the version of the tool to run. must +// - release: required. the version of the tool to run. must // already be downloaded. // - releaseOverride: optional. if provided, a release name to force // us to use when restarting (this functions exactly like --release @@ -1226,6 +1233,7 @@ commandName + ": You're not in a Meteor project directory.\n" + } catch (e) { if (e === main.ShowUsage || e === main.WaitForExit || e === main.SpringboardToLatestRelease || + e === main.SpringboardToSpecificReleaseg || e === main.WaitForExit) { throw new Error( "you meant 'throw new main.Foo', not 'throw main.Foo'"); @@ -1247,6 +1255,20 @@ commandName + ": You're not in a Meteor project directory.\n" + } springboard(latestRelease, latestRelease.name); // (does not return) + } else if (e instanceof main.SpringboardToSpecificRelease) { + // Springboard to a specific release. + var nextRelease; + var relName = e.releaseRecord.track + "@" + e.releaseRecord.version; + var messages = buildmessage.capture(function () { + nextRelease = release.load(relName); + }); + if (messages.hasMessages()) { + process.stderr.write("=> " + e.msg + ":\n\n"); + process.stderr.write(messages.formatMessages()); + process.exit(1); + } + springboard(nextRelease, relName); + // (does not return) } else if (e instanceof main.WaitForExit) { return; } else if (e instanceof main.ExitWithCode) {