chore(GameIdentifier): remove engineVersion; rename String version to displayVersion (#655)

master
Kevin Turner 2021-08-12 14:04:34 -07:00 committed by GitHub
parent 4327450c1e
commit d40284dd64
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 39 additions and 159 deletions

View File

@ -18,18 +18,14 @@ import org.terasology.launcher.util.DownloadUtils;
import org.terasology.launcher.util.FileUtils;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Comparator;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
public class GameManager {
@ -55,7 +51,7 @@ public class GameManager {
private String getFileNameFor(GameRelease release) {
GameIdentifier id = release.getId();
String profileString = id.getProfile().toString().toLowerCase();
String versionString = id.getVersion();
String versionString = id.getDisplayVersion();
String buildString = id.getBuild().toString().toLowerCase();
return "terasology-" + profileString + "-" + versionString + "-" + buildString + ".zip";
}
@ -129,7 +125,7 @@ public class GameManager {
}
public Path getInstallDirectory(GameIdentifier id) {
return installDirectory.resolve(id.getProfile().name()).resolve(id.getBuild().name()).resolve(id.getVersion());
return installDirectory.resolve(id.getProfile().name()).resolve(id.getBuild().name()).resolve(id.getDisplayVersion());
}
/**
@ -164,85 +160,6 @@ public class GameManager {
logger.debug("Directory does not match expected profile/build names: {}", versionDirectory, e);
return null;
}
return getInstalledVersion(profile, build, versionDirectory);
}
private static GameIdentifier getInstalledVersion(Profile profile, Build build, Path versionDirectory) {
Path engineJar;
try (var jarMatches = Files.find(versionDirectory, 3, GameManager::matchEngineJar)) {
var matches = jarMatches.collect(Collectors.toUnmodifiableSet());
if (matches.isEmpty()) {
logger.warn("Could not find engine jar in {}", versionDirectory);
return null;
} else if (matches.size() > 1) {
logger.warn("Ambiguous results while looking for engine jar in {}: {}", versionDirectory, matches);
return null;
}
engineJar = matches.iterator().next();
} catch (IOException e) {
logger.error("Error while looking for engine jar in {}", versionDirectory, e);
return null;
}
Properties versionInfo;
try {
versionInfo = getVersionPropertiesFromJar(engineJar);
} catch (IOException e) {
logger.error("Error while looking for version in {}.", engineJar, e);
return null;
}
return GameIdentifier.fromVersionInfo(versionInfo, build, profile).orElse(null);
}
/**
* A {@link java.util.function.BiPredicate BiPredicate} matcher function for the Terasology Engine JAR file.
* <p>
* The Terasology Engine JAR file needs to be located within a {@code lib} or {@code libs} directory.
* Its file name must be of the form {@code engine*.jar}.
*
* @param path the path of the file to match
* @param basicFileAttributes the file attributes of the file to match
* @return true iff the file matches the expected pattern for Terasology's engine JAR
*/
private static boolean matchEngineJar(Path path, BasicFileAttributes basicFileAttributes) {
// Are there path-matching utilities to simplify this?
final var libPaths = Set.of(Path.of("lib"), Path.of("libs"));
var parent = path.getParent();
var file = path.getFileName().toString();
return Files.isDirectory(parent)
&& libPaths.contains(parent.getFileName())
&& file.endsWith(".jar")
&& file.startsWith("engine");
}
/**
* Retrieve any {@code versionInfo.properties} from the given JAR file if it exists.
* <p>
* This method assumes that there is exactly one file named {@code versionInfo.properties} in the JAR.
* Note, that this will try to parse <b>any</b> file matching the naming pattern into a {@link Properties} object
* and return it.
*
* @param jarLocation the path to the JAR file containing a version info file
* @return the version info properties object
* @throws IOException if an I/O error occurs
* @throws FileNotFoundException if the version info file could not be found in the JAR file
*/
private static Properties getVersionPropertiesFromJar(Path jarLocation) throws IOException {
try (var jar = new JarFile(jarLocation.toFile())) {
var versionEntry = jar.stream().filter(entry ->
entry.getName().endsWith("versionInfo.properties") // FIXME: use const
).findAny();
if (versionEntry.isEmpty()) {
throw new FileNotFoundException("Found no versionInfo.properties in " + jarLocation);
}
var properties = new Properties();
try (var input = jar.getInputStream(versionEntry.get())) {
properties.load(input);
}
return properties;
}
return new GameIdentifier(versionDirectory.getFileName().toString(), build, profile);
}
}

View File

@ -30,6 +30,6 @@ public enum VersionHistory {
}
boolean isProvidedBy(GameIdentifier version) {
return isProvidedBy(version.getEngineVersion());
return isProvidedBy(version.getVersion()); // FIXME ASAP: obsoleted by PR#654
}
}

View File

@ -5,12 +5,11 @@ package org.terasology.launcher.model;
import com.google.common.base.MoreObjects;
import com.vdurmont.semver4j.Semver;
import com.vdurmont.semver4j.SemverException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
/**
* Uniquely identify a game release.
@ -28,70 +27,27 @@ public class GameIdentifier {
private static final Logger logger = LoggerFactory.getLogger(GameIdentifier.class);
final String version;
final Semver engineVersion;
final String displayVersion;
final Build build;
final Profile profile;
public GameIdentifier(String version, Semver engineVersion, Build build, Profile profile) {
this.version = version;
this.engineVersion = engineVersion;
public GameIdentifier(String displayVersion, Build build, Profile profile) {
this.displayVersion = displayVersion;
this.build = build;
this.profile = profile;
}
public GameIdentifier(String version, String engineVersion, Build build, Profile profile) {
this(version, new Semver(engineVersion, Semver.SemverType.IVY), build, profile);
}
/**
* Attempt to create a game identifier for a specific {@link Build} and {@link Profile} from a version info file.
* <p>
* The version info properties
* <ul>
* <li> MUST contain a {@code displayVersion} entry (any string)
* <li> MUST contain a {@code engineVersion} entry holding a valid SemVer
* <li> MAY contain a {@code buildNumber}
* </ul>
* Other properties are ignored.
* <p>
* If the version info properties contain a non-empty {@code buildNumber} it is appended to the
* {@code displayVersion} to form the {@link GameIdentifier#getVersion()}.
*
* @param versionInfo the Terasology distribution version info; must contain a {@code displayVersion} (any string)
* and a valid SemVer in {@code engineVersion}
* @param build the build variant of the game release
* @param profile the build profile of the game release
* @return Some identifier if the version info meets the requirements; empty otherwise
*/
public static Optional<GameIdentifier> fromVersionInfo(Properties versionInfo, Build build, Profile profile) {
var displayVersion = versionInfo.getProperty("displayVersion");
if (displayVersion == null) {
return Optional.empty();
}
// Append the build number to the display version to ensure that the version string is unique for two different
// builds. This is necessary because the display version generated by the CI build is the same of subsequent
// builds...
var buildNumber = versionInfo.getProperty("buildNumber");
if (buildNumber != null && !buildNumber.isEmpty()) {
displayVersion += "+" + buildNumber;
}
public Semver getVersion() {
// compatibility shim until we have GameIdentifier.version
try {
Semver engineVersion = new Semver(versionInfo.getProperty("engineVersion"), Semver.SemverType.IVY);
return Optional.of(new GameIdentifier(displayVersion, engineVersion, build, profile));
} catch (RuntimeException e) {
logger.warn("versionInfo.properties displayVersion=\"{}\" engineVersion=\"{}\" " +
"is not a valid version.", displayVersion, versionInfo.getProperty("engineVersion"), e);
return Optional.empty();
return new Semver(displayVersion, Semver.SemverType.LOOSE);
} catch (SemverException e) {
return new Semver("0.0.1-" + displayVersion, Semver.SemverType.LOOSE); // FIXME ASAP
}
}
public String getVersion() {
return version;
}
public Semver getEngineVersion() {
return engineVersion;
public String getDisplayVersion() {
return displayVersion;
}
public Build getBuild() {
@ -111,22 +67,20 @@ public class GameIdentifier {
return false;
}
GameIdentifier that = (GameIdentifier) o;
return version.equals(that.version)
&& engineVersion.equals(that.engineVersion)
return displayVersion.equals(that.displayVersion)
&& build == that.build
&& profile == that.profile;
}
@Override
public int hashCode() {
return Objects.hash(version, build, profile, engineVersion);
return Objects.hash(displayVersion, build, profile);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("version", version)
.add("engineVersion", engineVersion)
.add("version", displayVersion)
.add("build", build)
.add("profile", profile)
.toString();

View File

@ -56,7 +56,7 @@ public class GithubRepositoryAdapter implements ReleaseRepository {
final URL url = new URL(gameAsset.map(GHAsset::getBrowserDownloadUrl).orElseThrow(() -> new IOException("Missing game asset.")));
final String changelog = ghRelease.getBody();
GameIdentifier id = new GameIdentifier(engineVersion.toString(), engineVersion, build, profile);
GameIdentifier id = new GameIdentifier(engineVersion.toString(), build, profile);
ReleaseMetadata metadata = new ReleaseMetadata(changelog, ghRelease.getPublished_at());
return new GameRelease(id, url, metadata);

View File

@ -96,7 +96,18 @@ class JenkinsRepositoryAdapter implements ReleaseRepository {
private Optional<GameIdentifier> computeIdentifierFrom(Jenkins.Build jenkinsBuildInfo) {
return Optional.ofNullable(client.getArtifactUrl(jenkinsBuildInfo, "versionInfo.properties"))
.map(client::requestProperties)
.flatMap(versionInfo -> GameIdentifier.fromVersionInfo(versionInfo, buildProfile, profile));
.map(versionInfo -> versionInfo.getProperty("displayVersion"))
.map(displayVersion -> {
// versionInfo.properties is created during the Engine build.
// jenkinsBuildInfo is the build of a Distribution.
//
// There may be multiple Distribution builds that come from the same Engine build.
//
// We can use the Engine's displayVersion, but we use the Distribution build number
// to ensure uniqueness.
String versionString = displayVersion + "+" + jenkinsBuildInfo.number;
return new GameIdentifier(versionString, buildProfile, profile);
});
}
private ReleaseMetadata computeReleaseMetadataFrom(Jenkins.Build jenkinsBuildInfo) {

View File

@ -1,4 +1,4 @@
// Copyright 2020 The Terasology Foundation
// Copyright 2021 The Terasology Foundation
// SPDX-License-Identifier: Apache-2.0
package org.terasology.launcher.ui;
@ -50,10 +50,10 @@ final class GameReleaseCell extends ListCell<GameRelease> {
String displayVersion;
if (id.getBuild().equals(Build.NIGHTLY)) {
setStyle("-fx-font-weight: normal");
displayVersion = "preview " + id.getVersion() + " (" + DATE_FORMAT.format(item.getTimestamp()) + ")";
displayVersion = "preview " + id.getDisplayVersion() + " (" + DATE_FORMAT.format(item.getTimestamp()) + ")";
} else {
setStyle("-fx-font-weight: bold");
displayVersion = "release " + id.getVersion();
displayVersion = "release " + id.getDisplayVersion();
}
setText(displayVersion);

View File

@ -61,7 +61,7 @@ public class TestGameStarter {
GameRelease release;
try {
release = new GameRelease(
new GameIdentifier("alpha-20", "5.1.0", Build.STABLE, Profile.OMEGA),
new GameIdentifier("alpha-20", Build.STABLE, Profile.OMEGA),
new URL("https://repository.example"),
new ReleaseMetadata(
"# CHANGES",

View File

@ -89,7 +89,7 @@ class JenkinsRepositoryAdapterTest {
// is unique for two different builds. This is necessary because the display version generated by the CI build
// is the same of subsequent builds...
final String expectedVersion = displayVersion + "+" + validResult.builds[0].number;
final GameIdentifier id = new GameIdentifier(expectedVersion, engineVersion, Build.STABLE, Profile.OMEGA);
final GameIdentifier id = new GameIdentifier(expectedVersion, Build.STABLE, Profile.OMEGA);
final ReleaseMetadata releaseMetadata = new ReleaseMetadata("", new Date(1604285977306L));
final GameRelease expected = new GameRelease(id, expectedArtifactUrl, releaseMetadata);

View File

@ -1,4 +1,4 @@
// Copyright 2020 The Terasology Foundation
// Copyright 2021 The Terasology Foundation
// SPDX-License-Identifier: Apache-2.0
package org.terasology.launcher.settings;
@ -157,11 +157,9 @@ class TestLauncherSettings {
@Test
void canRecognizeLastGamePlayed() {
final var displayVersion = "alpha-20";
final var engineVersion = "5.0.1-SNAPSHOT";
GameIdentifier id = new GameIdentifier(displayVersion, engineVersion, Build.NIGHTLY, Profile.OMEGA);
GameIdentifier id = new GameIdentifier(displayVersion, Build.NIGHTLY, Profile.OMEGA);
// Second object so as not to rely on instance identity.
GameIdentifier expectedId = new GameIdentifier(displayVersion, engineVersion,
Build.NIGHTLY, Profile.OMEGA);
GameIdentifier expectedId = new GameIdentifier(displayVersion, Build.NIGHTLY, Profile.OMEGA);
baseLauncherSettings.setLastPlayedGameVersion(id);
assertEquals(Optional.of(expectedId), baseLauncherSettings.getLastPlayedGameVersion());