public static bool MigrateAssetIfNeeded(AssetMigrationContext context, PackageLoadingAssetFile loadAsset, string dependencyName, PackageVersion untilVersion = null) { var assetFullPath = loadAsset.FilePath.FullPath; // Determine if asset was Yaml or not var assetFileExtension = Path.GetExtension(assetFullPath); if (assetFileExtension == null) { return(false); } assetFileExtension = assetFileExtension.ToLowerInvariant(); var serializer = AssetSerializer.FindSerializer(assetFileExtension); if (!(serializer is AssetYamlSerializer)) { return(false); } // We've got a Yaml asset, let's get expected and serialized versions var serializedVersion = PackageVersion.Zero; PackageVersion expectedVersion; Type assetType; // Read from Yaml file the asset version and its type (to get expected version) // Note: It tries to read as few as possible (SerializedVersion is expected to be right after Id, so it shouldn't try to read further than that) using (var assetStream = loadAsset.OpenStream()) using (var streamReader = new StreamReader(assetStream)) { var yamlEventReader = new EventReader(new Parser(streamReader)); // Skip header yamlEventReader.Expect <StreamStart>(); yamlEventReader.Expect <DocumentStart>(); var mappingStart = yamlEventReader.Expect <MappingStart>(); var yamlSerializerSettings = YamlSerializer.GetSerializerSettings(); var tagTypeRegistry = yamlSerializerSettings.TagTypeRegistry; bool typeAliased; assetType = tagTypeRegistry.TypeFromTag(mappingStart.Tag, out typeAliased); var expectedVersions = AssetRegistry.GetCurrentFormatVersions(assetType); expectedVersion = expectedVersions?.FirstOrDefault(x => x.Key == dependencyName).Value ?? PackageVersion.Zero; Scalar assetKey; while ((assetKey = yamlEventReader.Allow <Scalar>()) != null) { // Only allow Id before SerializedVersion if (assetKey.Value == nameof(Asset.Id)) { yamlEventReader.Skip(); } else if (assetKey.Value == nameof(Asset.SerializedVersion)) { // Check for old format: only a scalar var scalarVersion = yamlEventReader.Allow <Scalar>(); if (scalarVersion != null) { serializedVersion = PackageVersion.Parse("0.0." + Convert.ToInt32(scalarVersion.Value, CultureInfo.InvariantCulture)); // Let's update to new format using (var yamlAsset = loadAsset.AsYamlAsset()) { yamlAsset.DynamicRootNode.RemoveChild(nameof(Asset.SerializedVersion)); AssetUpgraderBase.SetSerializableVersion(yamlAsset.DynamicRootNode, dependencyName, serializedVersion); var baseBranch = yamlAsset.DynamicRootNode["~Base"]; if (baseBranch != null) { var baseAsset = baseBranch["Asset"]; if (baseAsset != null) { baseAsset.RemoveChild(nameof(Asset.SerializedVersion)); AssetUpgraderBase.SetSerializableVersion(baseAsset, dependencyName, serializedVersion); } } } } else { // New format: package => version mapping yamlEventReader.Expect <MappingStart>(); while (!yamlEventReader.Accept <MappingEnd>()) { var packageName = yamlEventReader.Expect <Scalar>().Value; var packageVersion = PackageVersion.Parse(yamlEventReader.Expect <Scalar>().Value); // For now, we handle only one dependency at a time if (packageName == dependencyName) { serializedVersion = packageVersion; } } yamlEventReader.Expect <MappingEnd>(); } break; } else { // If anything else than Id or SerializedVersion, let's stop break; } } } if (serializedVersion > expectedVersion) { // Try to open an asset newer than what we support (probably generated by a newer Xenko) throw new InvalidOperationException($"Asset of type {assetType} has been serialized with newer version {serializedVersion}, but only version {expectedVersion} is supported. Was this asset created with a newer version of Xenko?"); } if (serializedVersion < expectedVersion) { // Perform asset upgrade context.Log.Verbose("{0} needs update, from version {1} to version {2}", Path.GetFullPath(assetFullPath), serializedVersion, expectedVersion); using (var yamlAsset = loadAsset.AsYamlAsset()) { var yamlRootNode = yamlAsset.RootNode; // Check if there is any asset updater var assetUpgraders = AssetRegistry.GetAssetUpgraders(assetType, dependencyName); if (assetUpgraders == null) { throw new InvalidOperationException($"Asset of type {assetType} should be updated from version {serializedVersion} to {expectedVersion}, but no asset migration path was found"); } // Instantiate asset updaters var currentVersion = serializedVersion; while (currentVersion != expectedVersion) { PackageVersion targetVersion; // This will throw an exception if no upgrader is available for the given version, exiting the loop in case of error. var upgrader = assetUpgraders.GetUpgrader(currentVersion, out targetVersion); // Stop if the next version would be higher than what is expected if (untilVersion != null && targetVersion > untilVersion) { break; } upgrader.Upgrade(context, dependencyName, currentVersion, targetVersion, yamlRootNode, loadAsset); currentVersion = targetVersion; } // Make sure asset is updated to latest version YamlNode serializedVersionNode; PackageVersion newSerializedVersion = null; if (yamlRootNode.Children.TryGetValue(new YamlScalarNode(nameof(Asset.SerializedVersion)), out serializedVersionNode)) { var newSerializedVersionForDefaultPackage = ((YamlMappingNode)serializedVersionNode).Children[new YamlScalarNode(dependencyName)]; newSerializedVersion = PackageVersion.Parse(((YamlScalarNode)newSerializedVersionForDefaultPackage).Value); } if (untilVersion == null && newSerializedVersion != expectedVersion) { throw new InvalidOperationException($"Asset of type {assetType} was migrated, but still its new version {newSerializedVersion} doesn't match expected version {expectedVersion}."); } context.Log.Info("{0} updated from version {1} to version {2}", Path.GetFullPath(assetFullPath), serializedVersion, expectedVersion); } return(true); } return(false); }
public static void SetSerializableVersion(dynamic asset, string dependencyName, PackageVersion value) { if (asset.IndexOf(nameof(Asset.SerializedVersion)) == -1) { asset.SerializedVersion = new YamlMappingNode(); // Ensure that it is stored right after the asset Id asset.MoveChild(nameof(Asset.SerializedVersion), asset.IndexOf(nameof(Asset.Id)) + 1); } asset.SerializedVersion[dependencyName] = value; }
public PackageUpgraderAttribute(string packageName, string packageMinimumVersion, string packageUpdatedVersionRange) { PackageName = packageName; PackageMinimumVersion = new PackageVersion(packageMinimumVersion); PackageVersionRange.TryParse(packageUpdatedVersionRange, out this.packageUpdatedVersionRange); }
private void UpgradeBase(AssetMigrationContext context, string dependencyName, PackageVersion currentVersion, PackageVersion targetVersion, dynamic assetBase, PackageLoadingAssetFile assetFile) { var baseAsset = assetBase[nameof(AssetBase.Asset)]; if (baseAsset != null) { UpgradeAsset(context, currentVersion, targetVersion, baseAsset, assetFile, OverrideUpgraderHint.Base); SetSerializableVersion(baseAsset, dependencyName, targetVersion); } }
protected abstract void UpgradeAsset(AssetMigrationContext context, PackageVersion currentVersion, PackageVersion targetVersion, dynamic asset, PackageLoadingAssetFile assetFile, OverrideUpgraderHint overrideHint);
/// <summary> /// Initializes a new instance of the <see cref="PackageVersionRange" /> class. /// </summary> /// <param name="minVersion">The minimum version.</param> /// <param name="minVersionInclusive">if set to <c>true</c> the minimum version is inclusive</param> public PackageVersionRange(PackageVersion minVersion, bool minVersionInclusive) { IsMinInclusive = minVersionInclusive; MinVersion = minVersion; }
public void Upgrade(AssetMigrationContext context, string dependencyName, PackageVersion currentVersion, PackageVersion targetVersion, YamlMappingNode yamlAssetNode, PackageLoadingAssetFile assetFile) { dynamic asset = new DynamicYamlMapping(yamlAssetNode); // upgrade the asset var baseBranch = asset[Asset.BaseProperty]; var basePartsBranch = asset[Asset.BasePartsProperty] as DynamicYamlArray; // Detect in what kind of override context we are var overrideHint = (baseBranch != null || (basePartsBranch != null && basePartsBranch.Node.Children.Count > 0)) ? OverrideUpgraderHint.Derived : OverrideUpgraderHint.Unknown; // Upgrade the asset UpgradeAsset(context, currentVersion, targetVersion, asset, assetFile, overrideHint); SetSerializableVersion(asset, dependencyName, targetVersion); // Upgrade its base if (baseBranch != null) { UpgradeBase(context, dependencyName, currentVersion, targetVersion, baseBranch, assetFile); } // Upgrade base parts if (basePartsBranch != null) { foreach (dynamic assetBase in basePartsBranch) { UpgradeBase(context, dependencyName, currentVersion, targetVersion, assetBase, assetFile); } } }
/// <summary> /// Tries to parse a version dependency. /// </summary> /// <param name="value">The version dependency as a string.</param> /// <param name="result">The parsed result.</param> /// <returns><c>true</c> if successfuly parsed, <c>false</c> otherwise.</returns> /// <exception cref="System.ArgumentNullException">value</exception> public static bool TryParse(string value, out PackageVersionRange result) { if (value == null) { throw new ArgumentNullException("value"); } var versionSpec = new PackageVersionRange(); value = value.Trim(); // First, try to parse it as a plain version string PackageVersion version; if (PackageVersion.TryParse(value, out version)) { // A plain version is treated as an inclusive minimum range result = new PackageVersionRange { MinVersion = version, IsMinInclusive = true }; return(true); } // It's not a plain version, so it must be using the bracket arithmetic range syntax result = null; // Fail early if the string is too short to be valid if (value.Length < 3) { return(false); } // The first character must be [ ot ( switch (value.First()) { case '[': versionSpec.IsMinInclusive = true; break; case '(': versionSpec.IsMinInclusive = false; break; default: return(false); } // The last character must be ] ot ) switch (value.Last()) { case ']': versionSpec.IsMaxInclusive = true; break; case ')': versionSpec.IsMaxInclusive = false; break; default: return(false); } // Get rid of the two brackets value = value.Substring(1, value.Length - 2); // Split by comma, and make sure we don't get more than two pieces string[] parts = value.Split(','); if (parts.Length > 2) { return(false); } if (parts.All(string.IsNullOrEmpty)) { // If all parts are empty, then neither of upper or lower bounds were specified. Version spec is of the format (,] return(false); } // If there is only one piece, we use it for both min and max string minVersionString = parts[0]; string maxVersionString = (parts.Length == 2) ? parts[1] : parts[0]; // Only parse the min version if it's non-empty if (!string.IsNullOrWhiteSpace(minVersionString)) { if (!PackageVersion.TryParse(minVersionString, out version)) { return(false); } versionSpec.MinVersion = version; } // Same deal for max if (!string.IsNullOrWhiteSpace(maxVersionString)) { if (!PackageVersion.TryParse(maxVersionString, out version)) { return(false); } versionSpec.MaxVersion = version; } // Successful parse! result = versionSpec; return(true); }
protected override void UpgradeAsset(AssetMigrationContext context, PackageVersion currentVersion, PackageVersion targetVersion, dynamic asset, PackageLoadingAssetFile assetFile, OverrideUpgraderHint overrideHint) { var rootPartIds = asset.Hierarchy.RootPartIds; int i = 0; foreach (dynamic rootPartId in rootPartIds) { rootPartIds[i++] = "ref!! " + rootPartId.ToString(); } asset.Hierarchy.RootParts = rootPartIds; asset.Hierarchy.RootPartIds = DynamicYamlEmpty.Default; }
public void Upgrade(AssetMigrationContext context, string dependencyName, PackageVersion currentVersion, PackageVersion targetVersion, YamlMappingNode yamlAssetNode, PackageLoadingAssetFile assetFile) { dynamic asset = new DynamicYamlMapping(yamlAssetNode); // upgrade the asset UpgradeAsset(context, currentVersion, targetVersion, asset, assetFile); SetSerializableVersion(asset, dependencyName, targetVersion); // upgrade its base var baseBranch = asset[Asset.BaseProperty]; if (baseBranch != null) { UpgradeBase(context, dependencyName, currentVersion, targetVersion, baseBranch, assetFile); } // upgrade base parts var basePartsBranch = asset[Asset.BasePartsProperty] as DynamicYamlArray; if (basePartsBranch != null) { foreach (dynamic assetBase in basePartsBranch) { UpgradeBase(context, dependencyName, currentVersion, targetVersion, assetBase, assetFile); } } }
/// <summary> /// Initializes a new instance of the <see cref="AssetFormatVersionAttribute"/> class. /// </summary> /// <param name="name">The dependency name.</param> /// <param name="version">The current format version of this asset.</param> /// <param name="minUpgradableVersion">The minimum format version that supports upgrade for this asset.</param> public AssetFormatVersionAttribute(string name, string version, string minUpgradableVersion = null) { Name = name; Version = PackageVersion.Parse(version); MinUpgradableVersion = PackageVersion.Parse(minUpgradableVersion ?? "0"); }
/// <summary> /// Initializes a new instance of the <see cref="PackageStore"/> class. /// </summary> /// <exception cref="System.InvalidOperationException">Unable to find a valid Paradox installation path</exception> private PackageStore(string installationPath = null, string defaultPackageName = "Paradox", string defaultPackageVersion = ParadoxVersion.CurrentAsText) { // 1. Try to use the specified installation path if (installationPath != null) { if (!IsRootDirectory(installationPath)) { throw new ArgumentException("Invalid Paradox installation path [{0}]".ToFormat(installationPath), "installationPath"); } globalInstallationPath = installationPath; } // TODO: these are currently hardcoded to Paradox DefaultPackageName = defaultPackageName; DefaultPackageVersion = new PackageVersion(defaultPackageVersion); // 2. Try to resolve an installation path from the path of this assembly // We need to be able to use the package manager from an official Paradox install as well as from a developer folder // Try to determine the root package manager from the current assembly var thisAssemblyLocation = typeof(PackageStore).Assembly.Location; var binDirectory = new FileInfo(thisAssemblyLocation).Directory; if (binDirectory != null && binDirectory.Parent != null && binDirectory.Parent.Parent != null) { var defaultPackageDirectoryTemp = binDirectory.Parent.Parent; // If we have a root directory, then store it as the default package directory if (IsPackageDirectory(defaultPackageDirectoryTemp.FullName, DefaultPackageName)) { defaultPackageDirectory = defaultPackageDirectoryTemp.FullName; } else { throw new InvalidOperationException("The current assembly [{0}] is not part of the package [{1}]".ToFormat(thisAssemblyLocation, DefaultPackageName)); } if (globalInstallationPath == null) { // Check if we have a regular distribution if (defaultPackageDirectoryTemp.Parent != null && IsRootDirectory(defaultPackageDirectoryTemp.Parent.FullName)) { globalInstallationPath = defaultPackageDirectoryTemp.Parent.FullName; } else if (IsRootDirectory(defaultPackageDirectory)) { // we have a dev distribution globalInstallationPath = defaultPackageDirectory; } } } else { throw new InvalidOperationException("The current assembly [{0}] must be loaded from a valid installation".ToFormat(thisAssemblyLocation)); } // 3. Try from the environement variable if (globalInstallationPath == null) { var rootDirectory = Environment.GetEnvironmentVariable(DefaultEnvironmentSdkDir); if (!string.IsNullOrWhiteSpace(rootDirectory) && IsRootDirectory(rootDirectory)) { globalInstallationPath = rootDirectory; } } // If there is no root, this is an error if (globalInstallationPath == null) { throw new InvalidOperationException("Unable to find a valid Paradox installation or dev path"); } // Preload default package var logger = new LoggerResult(); var defaultPackageFile = GetPackageFile(defaultPackageDirectory, DefaultPackageName); defaultPackage = Package.Load(logger, defaultPackageFile, GetDefaultPackageLoadParameters()); if (defaultPackage == null) { throw new InvalidOperationException("Error while loading default package from [{0}]: {1}".ToFormat(defaultPackageFile, logger.ToText())); } defaultPackage.IsSystem = true; // A flag variable just to know if it is a bare bone development directory isDev = defaultPackageDirectory != null && IsRootDevDirectory(defaultPackageDirectory); // Check if we are in a root directory with store/packages facilities if (NugetStore.IsStoreDirectory(globalInstallationPath)) { packagesDirectory = UPath.Combine(globalInstallationPath, (UDirectory)NugetStore.DefaultGamePackagesDirectory); store = new NugetStore(globalInstallationPath) { DefaultPackageId = DefaultPackageName }; } }