Compare assembly and file versions on minor roll forward (dotnet/core-setup#3704)
authorSteve Harter <steveharter@users.noreply.github.com>
Fri, 23 Feb 2018 17:26:09 +0000 (11:26 -0600)
committerGitHub <noreply@github.com>
Fri, 23 Feb 2018 17:26:09 +0000 (11:26 -0600)
Add DependencyModel\deps.json support for assemblyVersion and fileVersion runtime assets and add conflict resolution to hostpolicy.dll that uses that metadata during minor\major roll-forward cases.

Commit migrated from https://github.com/dotnet/core-setup/commit/348572725673b54aca474974a6ea4f05a2d479b3

27 files changed:
src/installer/corehost/cli/args.h
src/installer/corehost/cli/deps_entry.cpp
src/installer/corehost/cli/deps_entry.h
src/installer/corehost/cli/deps_format.cpp
src/installer/corehost/cli/deps_format.h
src/installer/corehost/cli/deps_resolver.cpp
src/installer/corehost/cli/deps_resolver.h
src/installer/corehost/cli/dll/CMakeLists.txt
src/installer/corehost/cli/fx_definition.cpp
src/installer/corehost/cli/fx_definition.h
src/installer/corehost/cli/fxr/CMakeLists.txt
src/installer/corehost/cli/fxr/fx_ver.cpp
src/installer/corehost/cli/version.cpp [new file with mode: 0644]
src/installer/corehost/cli/version.h [new file with mode: 0644]
src/installer/corehost/common/utils.cpp
src/installer/corehost/common/utils.h
src/installer/managed/Microsoft.Extensions.DependencyModel/CollectionExtensions.cs
src/installer/managed/Microsoft.Extensions.DependencyModel/DependencyContextExtensions.cs
src/installer/managed/Microsoft.Extensions.DependencyModel/DependencyContextJsonReader.cs
src/installer/managed/Microsoft.Extensions.DependencyModel/DependencyContextStrings.cs
src/installer/managed/Microsoft.Extensions.DependencyModel/DependencyContextWriter.cs
src/installer/managed/Microsoft.Extensions.DependencyModel/RuntimeAssetGroup.cs
src/installer/managed/Microsoft.Extensions.DependencyModel/RuntimeFile.cs [new file with mode: 0644]
src/installer/test/HostActivationTests/GivenThatICareAboutMultilevelSharedFxLookup.cs
src/installer/test/Microsoft.Extensions.DependencyModel.Tests/DependencyContextJsonReaderTest.cs
src/installer/test/Microsoft.Extensions.DependencyModel.Tests/DependencyContextJsonWriterTests.cs
src/installer/test/Microsoft.Extensions.DependencyModel.Tests/JsonAssetions.cs

index 6416113..49218fb 100644 (file)
@@ -16,6 +16,7 @@ struct probe_config_t
     bool patch_roll_fwd;
     bool prerelease_roll_fwd;
     const deps_json_t* probe_deps_json;
+    int fx_level;
 
     bool only_runtime_assets;
     bool only_serviceable_assets;
@@ -24,18 +25,20 @@ struct probe_config_t
 
     void print() const
     {
-        trace::verbose(_X("probe_config_t: probe=[%s]  deps-json=[%p] deps-dir-probe=[%d]"),
-            probe_dir.c_str(), probe_deps_json, probe_publish_dir);
+        trace::verbose(_X("probe_config_t: probe=[%s] deps-dir-probe=[%d]"),
+            probe_dir.c_str(), probe_publish_dir);
     }
 
     probe_config_t(
         const pal::string_t& probe_dir,
         const deps_json_t* probe_deps_json,
+        int fx_level,
         bool only_serviceable_assets,
         bool only_runtime_assets,
         bool probe_publish_dir)
         : probe_dir(probe_dir)
         , probe_deps_json(probe_deps_json)
+        , fx_level(fx_level)
         , only_serviceable_assets(only_serviceable_assets)
         , only_runtime_assets(only_runtime_assets)
         , probe_publish_dir(probe_publish_dir)
@@ -50,29 +53,39 @@ struct probe_config_t
             !probe_publish_dir;
     }
 
+    bool is_fx() const
+    {
+        return (probe_deps_json != nullptr);
+    }
+
+    bool is_app() const
+    {
+        return probe_publish_dir;
+    }
+
     static probe_config_t svc_ni(const pal::string_t& dir)
     {
-        return probe_config_t(dir, nullptr, true, true, false);
+        return probe_config_t(dir, nullptr, -1, true, true, false);
     }
 
     static probe_config_t svc(const pal::string_t& dir)
     {
-        return probe_config_t(dir, nullptr, true, false, false);
+        return probe_config_t(dir, nullptr, -1, true, false, false);
     }
 
-    static probe_config_t fx(const pal::string_t& dir, const deps_json_t* deps)
+    static probe_config_t fx(const pal::string_t& dir, const deps_json_t* deps, int fx_level)
     {
-        return probe_config_t(dir, deps, false, false, false);
+        return probe_config_t(dir, deps, fx_level, false, false, false);
     }
 
     static probe_config_t lookup(const pal::string_t& dir)
     {
-        return probe_config_t(dir, nullptr, false, false, false);
+        return probe_config_t(dir, nullptr, -1, false, false, false);
     }
 
     static probe_config_t published_deps_dir()
     {
-        return probe_config_t(_X(""), nullptr, false, false, true);
+        return probe_config_t(_X(""), nullptr, 0, false, false, true);
     }
 };
 
index 51fd7c1..576e297 100644 (file)
@@ -21,7 +21,7 @@ bool deps_entry_t::to_path(const pal::string_t& base, bool look_in_base, pal::st
 
     // Entry relative path contains '/' separator, sanitize it to use
     // platform separator. Perf: avoid extra copy if it matters.
-    pal::string_t pal_relative_path = relative_path;
+    pal::string_t pal_relative_path = asset.relative_path;
     if (_X('/') != DIR_SEPARATOR)
     {
         replace_char(&pal_relative_path, _X('/'), DIR_SEPARATOR);
@@ -64,7 +64,7 @@ bool deps_entry_t::to_dir_path(const pal::string_t& base, pal::string_t* str) co
 {
     if (asset_type == asset_types::resources)
     {
-        pal::string_t pal_relative_path = relative_path;
+        pal::string_t pal_relative_path = asset.relative_path;
         if (_X('/') != DIR_SEPARATOR)
         {
             replace_char(&pal_relative_path, _X('/'), DIR_SEPARATOR);
@@ -83,7 +83,7 @@ bool deps_entry_t::to_dir_path(const pal::string_t& base, pal::string_t* str) co
         
         pal::string_t base_ietf_dir = base;
         append_path(&base_ietf_dir, ietf.c_str());
-        trace::verbose(_X("Detected a resource asset, will query dir/ietf-tag/resource base: %s asset: %s"), base_ietf_dir.c_str(), asset_name.c_str());
+        trace::verbose(_X("Detected a resource asset, will query dir/ietf-tag/resource base: %s asset: %s"), base_ietf_dir.c_str(), asset.name.c_str());
         return to_path(base_ietf_dir, true, str);
     }
     return to_path(base, true, str);
index 727700d..ec6b262 100644 (file)
@@ -8,6 +8,23 @@
 #include <array>
 #include <vector>
 #include "pal.h"
+#include "version.h"
+
+struct deps_asset_t
+{
+    deps_asset_t() : deps_asset_t(_X(""), version_t(), version_t()) { }
+
+    deps_asset_t(const pal::string_t& relative_path, const version_t& assembly_version, const version_t& file_version)
+        : relative_path(get_replaced_char(relative_path, _X('\\'), _X('/'))) // Deps file does not follow spec. It uses '\\', should use '/'
+        , assembly_version(assembly_version)
+        , file_version(file_version)
+        , name(get_filename_without_ext(relative_path)) { }
+
+    pal::string_t name;
+    pal::string_t relative_path;
+    version_t assembly_version;
+    version_t file_version;
+};
 
 struct deps_entry_t
 {
@@ -30,12 +47,10 @@ struct deps_entry_t
     pal::string_t library_hash_path;
     pal::string_t runtime_store_manifest_list;
     asset_types asset_type;
-    pal::string_t asset_name;
-    pal::string_t relative_path;
+    deps_asset_t asset;
     bool is_serviceable;
     bool is_rid_specific;
 
-
     // Given a "base" dir, yield the filepath within this directory or relative to this directory based on "look_in_base"
     bool to_path(const pal::string_t& base, bool look_in_base, pal::string_t* str) const;
 
@@ -47,7 +62,6 @@ struct deps_entry_t
 
     // Given a "base" dir, yield the relative path with package name, version in the package layout.
     bool to_full_path(const pal::string_t& root, pal::string_t* str) const;
-
 };
 
 #endif // __DEPS_ENTRY_H_
index 6984f95..80c7523 100644 (file)
@@ -17,30 +17,39 @@ const std::array<const pal::char_t*, deps_entry_t::asset_types::count> deps_entr
 
 const deps_entry_t& deps_json_t::try_ni(const deps_entry_t& entry) const
 {
-    if (m_ni_entries.count(entry.asset_name))
+    if (m_ni_entries.count(entry.asset.name))
     {
-        int index = m_ni_entries.at(entry.asset_name);
+        int index = m_ni_entries.at(entry.asset.name);
         return m_deps_entries[deps_entry_t::asset_types::runtime][index];
     }
     return entry;
 }
 
-pal::string_t deps_json_t::get_optional_path(
+pal::string_t deps_json_t::get_optional_property(
     const json_object& properties,
     const pal::string_t& key) const
 {
-    pal::string_t path;
+    pal::string_t value;
 
     const auto& iter = properties.find(key);
 
     if (iter != properties.end())
     {
-        path = iter->second.as_string();
+        value = iter->second.as_string();
+    }
 
-        if (_X('/') != DIR_SEPARATOR)
-        {
-            replace_char(&path, _X('/'), DIR_SEPARATOR);
-        }
+    return value;
+}
+
+pal::string_t deps_json_t::get_optional_path(
+    const json_object& properties,
+    const pal::string_t& key) const
+{
+    pal::string_t path = get_optional_property(properties, key);
+
+    if (path.length() > 0 && _X('/') != DIR_SEPARATOR)
+    {
+        replace_char(&path, _X('/'), DIR_SEPARATOR);
     }
 
     return path;
@@ -50,7 +59,7 @@ void deps_json_t::reconcile_libraries_with_targets(
     const pal::string_t& deps_path,
     const json_value& json,
     const std::function<bool(const pal::string_t&)>& library_exists_fn,
-    const std::function<const std::vector<pal::string_t>&(const pal::string_t&, int, bool*)>& get_rel_paths_by_asset_type_fn)
+    const std::function<const vec_asset_t&(const pal::string_t&, int, bool*)>& get_assets_fn)
 {
     pal::string_t deps_file = get_filename(deps_path);
 
@@ -77,10 +86,10 @@ void deps_json_t::reconcile_libraries_with_targets(
         for (int i = 0; i < deps_entry_t::s_known_asset_types.size(); ++i)
         {
             bool rid_specific = false;
-            for (const auto& rel_path : get_rel_paths_by_asset_type_fn(library.first, i, &rid_specific))
+            for (const auto& asset : get_assets_fn(library.first, i, &rid_specific))
             {
                 bool ni_dll = false;
-                auto asset_name = get_filename_without_ext(rel_path);
+                auto asset_name = get_filename_without_ext(asset.relative_path);
                 if (ends_with(asset_name, _X(".ni"), false))
                 {
                     ni_dll = true;
@@ -96,33 +105,30 @@ void deps_json_t::reconcile_libraries_with_targets(
                 entry.library_path = library_path;
                 entry.library_hash_path = library_hash_path;
                 entry.runtime_store_manifest_list = runtime_store_manifest_list;
-                entry.asset_name = asset_name;
                 entry.asset_type = (deps_entry_t::asset_types) i;
-                entry.relative_path = rel_path;
                 entry.is_serviceable = serviceable;
                 entry.is_rid_specific = rid_specific;
                 entry.deps_file = deps_file;
-
-                // TODO: Deps file does not follow spec. It uses '\\', should use '/'
-                replace_char(&entry.relative_path, _X('\\'), _X('/'));
+                entry.asset = asset;
 
                 m_deps_entries[i].push_back(entry);
 
                 if (ni_dll)
                 {
-                    m_ni_entries[entry.asset_name] = m_deps_entries
+                    m_ni_entries[entry.asset.name] = m_deps_entries
                         [deps_entry_t::asset_types::runtime].size() - 1;
                 }
 
-                trace::info(_X("Parsed %s deps entry %d for asset name: %s from %s: %s, version: %s, relpath: %s"),
+                trace::info(_X("Parsed %s deps entry %d for asset name: %s from %s: %s, library version: %s, relpath: %s, assemblyVersion %s, fileVersion %s"),
                     deps_entry_t::s_known_asset_types[i],
                     m_deps_entries[i].size() - 1,
-                    entry.asset_name.c_str(),
+                    entry.asset.name.c_str(),
                     entry.library_type.c_str(),
                     entry.library_name.c_str(),
                     entry.library_version.c_str(),
-                    entry.relative_path.c_str());
-                
+                    entry.asset.relative_path.c_str(),
+                    entry.asset.assembly_version.as_str().c_str(),
+                    entry.asset.file_version.as_str().c_str());
             }
         }
     }
@@ -228,7 +234,33 @@ bool deps_json_t::process_runtime_targets(const json_value& json, const pal::str
                 if (pal::strcasecmp(type.c_str(), deps_entry_t::s_known_asset_types[i]) == 0)
                 {
                     const auto& rid = file.second.at(_X("rid")).as_string();
-                    assets.libs[package.first].rid_assets[rid].by_type[i].vec.push_back(file.first);
+
+                    version_t assembly_version, file_version;
+                    const auto& properties = file.second.as_object();
+
+                    pal::string_t assembly_version_str = get_optional_property(properties, _X("assemblyVersion"));
+                    if (assembly_version_str.length() > 0)
+                    {
+                        version_t::parse(assembly_version_str, &assembly_version);
+                    }
+
+                    pal::string_t file_version_str = get_optional_property(properties, _X("fileVersion"));
+                    if (file_version_str.length() > 0)
+                    {
+                        version_t::parse(file_version_str, &file_version);
+                    }
+
+                    deps_asset_t asset(file.first, assembly_version, file_version);
+
+                    trace::info(_X("Adding runtimeTargets %s asset %s rid=%s assemblyVersion=%s fileVersion=%s from %s"),
+                        deps_entry_t::s_known_asset_types[i],
+                        asset.relative_path.c_str(),
+                        rid.c_str(),
+                        asset.assembly_version.as_str().c_str(),
+                        asset.file_version.as_str().c_str(),
+                        package.first.c_str());
+
+                    assets.libs[package.first].rid_assets[rid][i].push_back(asset);
                 }
             }
         }
@@ -255,8 +287,31 @@ bool deps_json_t::process_targets(const json_value& json, const pal::string_t& t
             {
                 for (const auto& file : iter->second.as_object())
                 {
-                    trace::info(_X("Adding %s asset %s from %s"), deps_entry_t::s_known_asset_types[i], file.first.c_str(), package.first.c_str());
-                    assets.libs[package.first].by_type[i].vec.push_back(file.first);
+                    const auto& properties = file.second.as_object();
+                    version_t assembly_version, file_version;
+
+                    pal::string_t assembly_version_str = get_optional_property(properties, _X("assemblyVersion"));
+                    if (assembly_version_str.length() > 0)
+                    {
+                        version_t::parse(assembly_version_str, &assembly_version);
+                    }
+
+                    pal::string_t file_version_str = get_optional_property(properties, _X("fileVersion"));
+                    if (file_version_str.length() > 0)
+                    {
+                        version_t::parse(file_version_str, &file_version);
+                    }
+
+                    deps_asset_t asset(file.first, assembly_version, file_version);
+
+                    trace::info(_X("Adding %s asset %s assemblyVersion=%s fileVersion=%s from %s"),
+                        deps_entry_t::s_known_asset_types[i],
+                        asset.relative_path.c_str(),
+                        asset.assembly_version.as_str().c_str(),
+                        asset.file_version.as_str().c_str(),
+                        package.first.c_str());
+
+                    assets.libs[package.first][i].push_back(asset);
                 }
             }
         }
@@ -280,15 +335,15 @@ bool deps_json_t::load_portable(const pal::string_t& deps_path, const json_value
         return m_rid_assets.libs.count(package) || m_assets.libs.count(package);
     };
 
-    const std::vector<pal::string_t> empty;
-    auto get_relpaths = [&](const pal::string_t& package, int type_index, bool* rid_specific) -> const std::vector<pal::string_t>& {
+    const vec_asset_t empty;
+    auto get_relpaths = [&](const pal::string_t& package, int type_index, bool* rid_specific) -> const vec_asset_t& {
 
         *rid_specific = false;
 
         // Is there any rid specific assets for this type ("native" or "runtime" or "resources")
         if (m_rid_assets.libs.count(package) && !m_rid_assets.libs[package].rid_assets.empty())
         {
-            const auto& assets_by_type = m_rid_assets.libs[package].rid_assets.begin()->second.by_type[type_index].vec;
+            const auto& assets_by_type = m_rid_assets.libs[package].rid_assets.begin()->second[type_index];
             if (!assets_by_type.empty())
             {
                 *rid_specific = true;
@@ -300,7 +355,7 @@ bool deps_json_t::load_portable(const pal::string_t& deps_path, const json_value
 
         if (m_assets.libs.count(package))
         {
-            return m_assets.libs[package].by_type[type_index].vec;
+            return m_assets.libs[package][type_index];
         }
 
         return empty;
@@ -322,9 +377,9 @@ bool deps_json_t::load_standalone(const pal::string_t& deps_path, const json_val
         return m_assets.libs.count(package);
     };
 
-    auto get_relpaths = [&](const pal::string_t& package, int type_index, bool* rid_specific) -> const std::vector<pal::string_t>& {
+    auto get_relpaths = [&](const pal::string_t& package, int type_index, bool* rid_specific) -> const vec_asset_t& {
         *rid_specific = false;
-        return m_assets.libs[package].by_type[type_index].vec;
+        return m_assets.libs[package][type_index];
     };
 
     reconcile_libraries_with_targets(deps_path, json, package_exists, get_relpaths);
index b0b6235..f98d4db 100644 (file)
@@ -16,8 +16,8 @@ class deps_json_t
 {
     typedef web::json::value json_value;
     typedef web::json::object json_object;
-    struct vec_t { std::vector<pal::string_t> vec; };
-    struct assets_t { std::array<vec_t, deps_entry_t::asset_types::count> by_type; };
+    typedef std::vector<deps_asset_t> vec_asset_t;
+    typedef std::array<vec_asset_t, deps_entry_t::asset_types::count> assets_t;
     struct deps_assets_t { std::unordered_map<pal::string_t, assets_t> libs; };
     struct rid_assets_t { std::unordered_map<pal::string_t, assets_t> rid_assets; };
     struct rid_specific_assets_t { std::unordered_map<pal::string_t, rid_assets_t> libs; };
@@ -90,8 +90,9 @@ private:
         const pal::string_t& deps_path,
         const json_value& json,
         const std::function<bool(const pal::string_t&)>& library_exists_fn,
-        const std::function<const std::vector<pal::string_t>&(const pal::string_t&, int, bool*)>& get_rel_paths_by_asset_type_fn);
+        const std::function<const vec_asset_t&(const pal::string_t&, int, bool*)>& get_assets_fn);
 
+    pal::string_t get_optional_property(const json_object& properties, const pal::string_t& key) const;
     pal::string_t get_optional_path(const json_object& properties, const pal::string_t& key) const;
 
     pal::string_t get_current_rid(const rid_fallback_graph_t& rid_fallback_graph);
@@ -102,7 +103,7 @@ private:
     deps_assets_t m_assets;
     rid_specific_assets_t m_rid_assets;
 
-       std::unordered_map<pal::string_t, int> m_ni_entries;
+    std::unordered_map<pal::string_t, int> m_ni_entries;
     rid_fallback_graph_t m_rid_fallback_graph;
     bool m_file_exists;
     bool m_valid;
index 8504d60..11592e7 100644 (file)
@@ -59,7 +59,6 @@ void add_unique_path(
         non_serviced->push_back(PATH_SEPARATOR);
     }
 
-
     existing->insert(real);
 }
 
@@ -87,15 +86,18 @@ pal::string_t get_deps_filename(const pal::string_t& path)
   // "asset_name" be part of the "items" paths.
   //
 void deps_resolver_t::add_tpa_asset(
-    const pal::string_t& asset_name,
-    const pal::string_t& asset_path,
-    dir_assemblies_t* items)
+    const deps_resolved_asset_t& resolved_asset,
+    name_to_resolved_asset_map_t* items)
 {
-    std::unordered_map<pal::string_t, pal::string_t>::iterator existing = items->find(asset_name);
+    name_to_resolved_asset_map_t::iterator existing = items->find(resolved_asset.asset.name);
     if (existing == items->end())
     {
-        trace::verbose(_X("Adding tpa entry: %s"), asset_path.c_str());
-        items->emplace(asset_name, asset_path);
+        trace::verbose(_X("Adding tpa entry: %s, AssemblyVersion: %s, FileVersion: %s"),
+            resolved_asset.resolved_path.c_str(),
+            resolved_asset.asset.assembly_version.as_str().c_str(),
+            resolved_asset.asset.file_version.as_str().c_str());
+
+        items->emplace(resolved_asset.asset.name, resolved_asset);
     }
 }
 
@@ -106,8 +108,9 @@ void deps_resolver_t::add_tpa_asset(
 void deps_resolver_t::get_dir_assemblies(
     const pal::string_t& dir,
     const pal::string_t& dir_name,
-    dir_assemblies_t* dir_assemblies)
+    name_to_resolved_asset_map_t* items)
 {
+    version_t empty;
     trace::verbose(_X("Adding files from %s dir %s"), dir_name.c_str(), dir.c_str());
 
     // Managed extensions in priority order, pick DLL over EXE and NI over IL.
@@ -137,9 +140,13 @@ void deps_resolver_t::get_dir_assemblies(
             }
 
             // Already added entry for this asset, by priority order skip this ext
-            if (dir_assemblies->count(file_name))
+            if (items->count(file_name))
             {
-                trace::verbose(_X("Skipping %s because the %s already exists in %s assemblies"), file.c_str(), dir_assemblies->find(file_name)->second.c_str(), dir_name.c_str());
+                trace::verbose(_X("Skipping %s because the %s already exists in %s assemblies"),
+                    file.c_str(),
+                    items->find(file_name)->second.asset.relative_path.c_str(),
+                    dir_name.c_str());
+
                 continue;
             }
 
@@ -151,8 +158,14 @@ void deps_resolver_t::get_dir_assemblies(
             }
             file_path.append(file);
 
-            trace::verbose(_X("Adding %s to %s assembly set from %s"), file_name.c_str(), dir_name.c_str(), file_path.c_str());
-            dir_assemblies->emplace(file_name, file_path);
+            trace::verbose(_X("Adding %s to %s assembly set from %s"),
+                file_name.c_str(),
+                dir_name.c_str(),
+                file_path.c_str());
+
+            deps_asset_t asset(file_name, empty, empty);
+            deps_resolved_asset_t resolved_asset(asset, file_path);
+            add_tpa_asset(resolved_asset, items);
         }
     }
 }
@@ -221,18 +234,19 @@ void deps_resolver_t::setup_probe_config(
         m_probes.push_back(probe_config_t::svc(ext_pkgs));
     }
 
+    // The published deps directory to be probed: either app or FX directory.
+    // The probe directory will be available at probe time.
+    m_probes.push_back(probe_config_t::published_deps_dir());
+
+    // The framework locations, starting with highest level framework.
     for (int i = 1; i < init.fx_definitions.size(); ++i)
     {
         if (pal::directory_exists(init.fx_definitions[i]->get_dir()))
         {
-            m_probes.push_back(probe_config_t::fx(init.fx_definitions[i]->get_dir(), &init.fx_definitions[i]->get_deps()));
+            m_probes.push_back(probe_config_t::fx(init.fx_definitions[i]->get_dir(), &init.fx_definitions[i]->get_deps(), i));
         }
     }
 
-    // The published deps directory to be probed: either app or FX directory.
-    // The probe directory will be available at probe time.
-    m_probes.push_back(probe_config_t::published_deps_dir());
-
     setup_shared_store_probes(init, args);
 
     for (const auto& probe : m_additional_probes)
@@ -263,13 +277,14 @@ void deps_resolver_t::setup_additional_probes(const std::vector<pal::string_t>&
  *   -- When a deps json based probe is performed, the deps entry's package name and version must match.
  *   -- When looking into a published dir, for rid specific assets lookup rid split folders; for non-rid assets lookup the layout dir.
  */
-bool deps_resolver_t::probe_deps_entry(const deps_entry_t& entry, const pal::string_t& deps_dir, pal::string_t* candidate)
+bool deps_resolver_t::probe_deps_entry(const deps_entry_t& entry, const pal::string_t& deps_dir, int fx_level, pal::string_t* candidate)
 {
     candidate->clear();
 
     for (const auto& config : m_probes)
     {
-        trace::verbose(_X("  Considering entry [%s/%s/%s] and probe dir [%s]"), entry.library_name.c_str(), entry.library_version.c_str(), entry.relative_path.c_str(), config.probe_dir.c_str());
+        trace::verbose(_X("  Considering entry [%s/%s/%s], probe dir [%s], probe fx level:%d, entry fx level:%d"),
+            entry.library_name.c_str(), entry.library_version.c_str(), entry.asset.relative_path.c_str(), config.probe_dir.c_str(), config.fx_level, fx_level);
 
         if (config.only_serviceable_assets && !entry.is_serviceable)
         {
@@ -283,33 +298,55 @@ bool deps_resolver_t::probe_deps_entry(const deps_entry_t& entry, const pal::str
         }
         pal::string_t probe_dir = config.probe_dir;
 
-        if (config.probe_deps_json)
+        if (config.is_fx())
         {
-            // If the deps json has the package name and version, then someone has already done rid selection and
-            // put the right asset in the dir. So checking just package name and version would suffice.
-            // No need to check further for the exact asset relative sub path.
-            if (config.probe_deps_json->has_package(entry.library_name, entry.library_version) && entry.to_dir_path(probe_dir, candidate))
+            assert(config.fx_level > 0);
+
+            // Only probe frameworks that are the same level or lower than the current entry because
+            // a lower-level fx should not have a dependency on a higher-level fx and because starting
+            // with fx_level allows it to override a higher-level fx location if the entry is newer.
+            // Note that fx_level 0 is the highest level (the app)
+            if (fx_level <= config.fx_level)
             {
-                trace::verbose(_X("    Probed deps json and matched '%s'"), candidate->c_str());
-                return true;
+                // If the deps json has the package name and version, then someone has already done rid selection and
+                // put the right asset in the dir. So checking just package name and version would suffice.
+                // No need to check further for the exact asset relative sub path.
+                if (config.probe_deps_json->has_package(entry.library_name, entry.library_version) && entry.to_dir_path(probe_dir, candidate))
+                {
+                    trace::verbose(_X("    Probed deps json and matched '%s'"), candidate->c_str());
+                    return true;
+                }
             }
-            trace::verbose(_X("    Skipping... probe in deps json failed"));
+
+            trace::verbose(_X("    Skipping... not found in deps json."));
         }
-        else if (config.probe_publish_dir)
+        else if (config.is_app())
         {
             // This is a published dir probe, so look up rid specific assets in the rid folders.
-            if (entry.is_rid_specific && entry.to_rel_path(deps_dir, candidate))
-            {
-                trace::verbose(_X("    Probed deps dir and matched '%s'"), candidate->c_str());
-                return true;
-            }
-            // Non-rid assets, lookup in the published dir.
-            if (!entry.is_rid_specific && entry.to_dir_path(deps_dir, candidate))
+            assert(config.fx_level == 0);
+
+            if (fx_level <= config.fx_level)
             {
-                trace::verbose(_X("    Probed deps dir and matched '%s'"), candidate->c_str());
-                return true;
+                if (entry.is_rid_specific)
+                {
+                    if (entry.to_rel_path(deps_dir, candidate))
+                    {
+                        trace::verbose(_X("    Probed deps dir and matched '%s'"), candidate->c_str());
+                        return true;
+                    }
+                }
+                else
+                {
+                    // Non-rid assets, lookup in the published dir.
+                    if (entry.to_dir_path(deps_dir, candidate))
+                    {
+                        trace::verbose(_X("    Probed deps dir and matched '%s'"), candidate->c_str());
+                        return true;
+                    }
+                }
             }
-            trace::verbose(_X("    Skipping... probe in deps dir '%s' failed"), deps_dir.c_str());
+
+            trace::verbose(_X("    Skipping... not found in deps dir '%s'"), deps_dir.c_str());
         }
         else if (entry.to_full_path(probe_dir, candidate))
         {
@@ -333,7 +370,7 @@ bool report_missing_assembly_in_manifest(const deps_entry_t& entry, bool continu
         continueResolving = true;
 
         trace::info(MissingAssemblyMessage.c_str(), _X("Info"),
-            entry.deps_file.c_str(), entry.library_name.c_str(), entry.library_version.c_str(), entry.relative_path.c_str());
+            entry.deps_file.c_str(), entry.library_name.c_str(), entry.library_version.c_str(), entry.asset.relative_path.c_str());
 
         if (showManifestListMessage)
         {
@@ -343,7 +380,7 @@ bool report_missing_assembly_in_manifest(const deps_entry_t& entry, bool continu
     else if (continueResolving)
     {
         trace::warning(MissingAssemblyMessage.c_str(), _X("Warning"),
-            entry.deps_file.c_str(), entry.library_name.c_str(), entry.library_version.c_str(), entry.relative_path.c_str());
+            entry.deps_file.c_str(), entry.library_name.c_str(), entry.library_version.c_str(), entry.asset.relative_path.c_str());
 
         if (showManifestListMessage)
         {
@@ -353,7 +390,7 @@ bool report_missing_assembly_in_manifest(const deps_entry_t& entry, bool continu
     else
     {
         trace::error(MissingAssemblyMessage.c_str(), _X("Error"),
-            entry.deps_file.c_str(), entry.library_name.c_str(), entry.library_version.c_str(), entry.relative_path.c_str());
+            entry.deps_file.c_str(), entry.library_name.c_str(), entry.library_version.c_str(), entry.asset.relative_path.c_str());
 
         if (showManifestListMessage)
         {
@@ -372,9 +409,9 @@ bool deps_resolver_t::resolve_tpa_list(
         std::unordered_set<pal::string_t>* breadcrumb)
 {
     const std::vector<deps_entry_t> empty(0);
-    dir_assemblies_t items;
+    name_to_resolved_asset_map_t items;
 
-    auto process_entry = [&](const pal::string_t& deps_dir, const deps_entry_t& entry) -> bool
+    auto process_entry = [&](const pal::string_t& deps_dir, const deps_entry_t& entry, int fx_level, bool compare_versions_on_duplicate) -> bool
     {
         if (breadcrumb != nullptr && entry.is_serviceable)
         {
@@ -383,23 +420,22 @@ bool deps_resolver_t::resolve_tpa_list(
         }
 
         // Ignore placeholders
-        if (ends_with(entry.relative_path, _X("/_._"), false))
+        if (ends_with(entry.asset.relative_path, _X("/_._"), false))
         {
             return true;
         }
 
-        pal::string_t candidate;
-
-        trace::info(_X("Processing TPA for deps entry [%s, %s, %s]"), entry.library_name.c_str(), entry.library_version.c_str(), entry.relative_path.c_str());
+        trace::info(_X("Processing TPA for deps entry [%s, %s, %s]"), entry.library_name.c_str(), entry.library_version.c_str(), entry.asset.relative_path.c_str());
 
-        pal::string_t asset_path;
+        pal::string_t resolved_path;
 
-        dir_assemblies_t::iterator existing = items.find(entry.asset_name);
+        name_to_resolved_asset_map_t::iterator existing = items.find(entry.asset.name);
         if (existing == items.end())
         {
-            if (probe_deps_entry(entry, deps_dir, &asset_path))
+            if (probe_deps_entry(entry, deps_dir, fx_level, &resolved_path))
             {
-                add_tpa_asset(entry.asset_name, asset_path, &items);
+                deps_resolved_asset_t resolved_asset(entry.asset, resolved_path);
+                add_tpa_asset(resolved_asset, &items);
                 return true;
             }
 
@@ -407,21 +443,57 @@ bool deps_resolver_t::resolve_tpa_list(
         }
         else
         {
-            // Verify the extension is the same as the previous verfied entry
-            if (get_deps_filename(entry.relative_path) == get_filename(existing->second))
+            // Verify the extension is the same as the previous verified entry
+            if (get_deps_filename(entry.asset.relative_path) != get_filename(existing->second.resolved_path))
             {
-                return true;
+                trace::error(_X(
+                    "Error:\n"
+                    "  An assembly specified in the application dependencies manifest (%s) has already been found but with a different file extension:\n"
+                    "    package: '%s', version: '%s'\n"
+                    "    path: '%s'\n"
+                    "    previously found assembly: '%s'"),
+                    entry.deps_file.c_str(),
+                    entry.library_name.c_str(),
+                    entry.library_version.c_str(),
+                    entry.asset.relative_path.c_str(),
+                    existing->second.resolved_path.c_str());
+
+                return false;
             }
 
-            trace::error(_X(
-                "Error:\n"
-                "  An assembly specified in the application dependencies manifest (%s) has already been found but with a different file extension:\n"
-                "    package: '%s', version: '%s'\n"
-                "    path: '%s'\n"
-                "    previously found assembly: '%s'"),
-                entry.deps_file.c_str(), entry.library_name.c_str(), entry.library_version.c_str(), entry.relative_path.c_str(), existing->second.c_str());
+            if (compare_versions_on_duplicate)
+            {
+                deps_resolved_asset_t* existing_entry = &existing->second;
 
-            return false;
+                // If deps entry is newer than existing, then see if it should be replaced
+                if (entry.asset.assembly_version > existing_entry->asset.assembly_version ||
+                    (entry.asset.assembly_version == existing_entry->asset.assembly_version && entry.asset.file_version > existing_entry->asset.file_version))
+                {
+                    if (probe_deps_entry(entry, deps_dir, fx_level, &resolved_path))
+                    {
+                        // If the path is the same, then no need to replace
+                        if (resolved_path != existing_entry->resolved_path)
+                        {
+                            trace::verbose(_X("Replacing deps entry [%s, AssemblyVersion:%s, FileVersion:%s] with [%s, AssemblyVersion:%s, FileVersion:%s]"),
+                                existing_entry->resolved_path.c_str(), existing_entry->asset.assembly_version.as_str().c_str(), existing_entry->asset.file_version.as_str().c_str(),
+                                resolved_path.c_str(), entry.asset.assembly_version.as_str().c_str(), entry.asset.file_version.as_str().c_str());
+
+                            existing_entry = nullptr;
+                            items.erase(existing);
+
+                            deps_asset_t asset(entry.asset.relative_path, entry.asset.assembly_version, entry.asset.file_version);
+                            deps_resolved_asset_t resolved_asset(asset, resolved_path);
+                            add_tpa_asset(resolved_asset, &items);
+                        }
+                    }
+                    else
+                    {
+                        return report_missing_assembly_in_manifest(entry);
+                    }
+                }
+            }
+
+            return true;
         }
     };
 
@@ -429,12 +501,15 @@ bool deps_resolver_t::resolve_tpa_list(
     // TODO: Remove: the deps should contain the managed DLL.
     // Workaround for: csc.deps.json doesn't have the csc.dll
     pal::string_t managed_app_asset = get_filename_without_ext(m_managed_app);
-    add_tpa_asset(managed_app_asset, m_managed_app, &items);
+    deps_asset_t asset(managed_app_asset, version_t(), version_t());
+    deps_resolved_asset_t resolved_asset(asset, m_managed_app);
+    add_tpa_asset(resolved_asset, &items);
 
+    // Add the app's entries
     const auto& deps_entries = get_deps().get_entries(deps_entry_t::asset_types::runtime);
     for (const auto& entry : deps_entries)
     {
-        if (!process_entry(m_app_dir, entry))
+        if (!process_entry(m_app_dir, entry, 0, false))
         {
             return false;
         }
@@ -444,14 +519,8 @@ bool deps_resolver_t::resolve_tpa_list(
     // add the app local assemblies to the TPA.
     if (!get_deps().exists())
     {
-        dir_assemblies_t local_assemblies;
-
         // Obtain the local assemblies in the app dir.
-        get_dir_assemblies(m_app_dir, _X("local"), &local_assemblies);
-        for (const auto& kv : local_assemblies)
-        {
-            add_tpa_asset(kv.first, kv.second, &items);
-        }
+        get_dir_assemblies(m_app_dir, _X("local"), &items);
     }
 
     // If additional deps files were specified that need to be treated as part of the
@@ -461,7 +530,7 @@ bool deps_resolver_t::resolve_tpa_list(
         auto additional_deps_entries = additional_deps->get_entries(deps_entry_t::asset_types::runtime);
         for (auto entry : additional_deps_entries)
         {
-            if (!process_entry(m_app_dir, entry))
+            if (!process_entry(m_app_dir, entry, 0, false))
             {
                 return false;
             }
@@ -471,10 +540,13 @@ bool deps_resolver_t::resolve_tpa_list(
     // Probe FX deps entries after app assemblies are added.
     for (int i = 1; i < m_fx_definitions.size(); ++i)
     {
-        const auto& fx_entries = m_portable ? m_fx_definitions[i]->get_deps().get_entries(deps_entry_t::asset_types::runtime) : empty;
-        for (const auto& entry : fx_entries)
+        // A minor\major roll-forward affects which layer wins
+        bool is_minor_or_major_roll_forward = m_fx_definitions[i]->did_minor_or_major_roll_forward_occur();
+
+        const auto& deps_entries = m_portable ? m_fx_definitions[i]->get_deps().get_entries(deps_entry_t::asset_types::runtime) : empty;
+        for (const auto& entry : deps_entries)
         {
-            if (!process_entry(m_fx_definitions[i]->get_dir(), entry))
+            if (!process_entry(m_fx_definitions[i]->get_dir(), entry, i, is_minor_or_major_roll_forward))
             {
                 return false;
             }
@@ -485,7 +557,7 @@ bool deps_resolver_t::resolve_tpa_list(
     for (const auto& item : items)
     {
         // Workaround for CoreFX not being able to resolve sym links.
-        pal::string_t real_asset_path = item.second;
+        pal::string_t real_asset_path = item.second.resolved_path;
         pal::realpath(&real_asset_path);
         output->append(real_asset_path);
         output->push_back(PATH_SEPARATOR);
@@ -503,12 +575,12 @@ void deps_resolver_t::init_known_entry_path(const deps_entry_t& entry, const pal
     {
         return;
     }
-    if (m_coreclr_path.empty() && ends_with(entry.relative_path, _X("/") + pal::string_t(LIBCORECLR_NAME), false))
+    if (m_coreclr_path.empty() && ends_with(entry.asset.relative_path, _X("/") + pal::string_t(LIBCORECLR_NAME), false))
     {
         m_coreclr_path = path;
         return;
     }
-    if (m_clrjit_path.empty() && ends_with(entry.relative_path, _X("/") + pal::string_t(LIBCLRJIT_NAME), false))
+    if (m_clrjit_path.empty() && ends_with(entry.asset.relative_path, _X("/") + pal::string_t(LIBCLRJIT_NAME), false))
     {
         m_clrjit_path = path;
         return;
@@ -627,27 +699,29 @@ bool deps_resolver_t::resolve_probe_dirs(
 
     pal::string_t candidate;
 
-    auto add_package_cache_entry = [&](const deps_entry_t& entry, const pal::string_t& deps_dir) -> bool
+    auto add_package_cache_entry = [&](const deps_entry_t& entry, const pal::string_t& deps_dir, int fx_level) -> bool
     {
         if (breadcrumb != nullptr && entry.is_serviceable)
         {
             breadcrumb->insert(entry.library_name + _X(",") + entry.library_version);
             breadcrumb->insert(entry.library_name);
         }
-        if (items.count(entry.asset_name))
+
+        if (items.count(entry.asset.name))
         {
             return true;
         }
+
         // Ignore placeholders
-        if (ends_with(entry.relative_path, _X("/_._"), false))
+        if (ends_with(entry.asset.relative_path, _X("/_._"), false))
         {
             return true;
         }
 
         trace::verbose(_X("Processing native/culture for deps entry [%s, %s, %s]"), 
-            entry.library_name.c_str(), entry.library_version.c_str(), entry.relative_path.c_str());
+            entry.library_name.c_str(), entry.library_version.c_str(), entry.asset.relative_path.c_str());
 
-        if (probe_deps_entry(entry, deps_dir, &candidate))
+        if (probe_deps_entry(entry, deps_dir, fx_level, &candidate))
         {
             init_known_entry_path(entry, candidate);
             add_unique_path(asset_type, action(candidate), &items, output, &non_serviced, core_servicing);
@@ -656,7 +730,7 @@ bool deps_resolver_t::resolve_probe_dirs(
         {
             // For standalone apps, apphost.exe will be renamed. Do not use the full package name
             // because of rid-fallback could happen (ex: CentOS falling back to RHEL)
-            if ((entry.asset_name == _X("apphost")) && ends_with(entry.library_name, _X(".Microsoft.NETCore.DotNetAppHost"), false))
+            if ((entry.asset.name == _X("apphost")) && ends_with(entry.library_name, _X(".Microsoft.NETCore.DotNetAppHost"), false))
             {
                 return report_missing_assembly_in_manifest(entry, true);
             }
@@ -671,7 +745,7 @@ bool deps_resolver_t::resolve_probe_dirs(
     const auto& entries = get_deps().get_entries(asset_type);
     for (const auto& entry : entries)
     {
-        if (!add_package_cache_entry(entry, m_app_dir))
+        if (!add_package_cache_entry(entry, m_app_dir, 0))
         {
             return false;
         }
@@ -684,7 +758,6 @@ bool deps_resolver_t::resolve_probe_dirs(
         add_unique_path(asset_type, m_app_dir, &items, output, &non_serviced, core_servicing);
 
         (void) library_exists_in_dir(m_app_dir, LIBCORECLR_NAME, &m_coreclr_path);
-
         (void) library_exists_in_dir(m_app_dir, LIBCLRJIT_NAME, &m_clrjit_path);
     }
 
@@ -694,20 +767,21 @@ bool deps_resolver_t::resolve_probe_dirs(
         const auto additional_deps_entries = additional_deps->get_entries(asset_type);
         for (const auto entry : additional_deps_entries)
         {
-            if (!add_package_cache_entry(entry, m_app_dir))
+            if (!add_package_cache_entry(entry, m_app_dir, 0))
             {
                 return false;
             }
         }
     }
-    
+
     // Add fx package locations to fx_dir
     for (int i = 1; i < m_fx_definitions.size(); ++i)
     {
         const auto& fx_entries = m_fx_definitions[i]->get_deps().get_entries(asset_type);
+
         for (const auto& entry : fx_entries)
         {
-            if (!add_package_cache_entry(entry, m_fx_definitions[i]->get_dir()))
+            if (!add_package_cache_entry(entry, m_fx_definitions[i]->get_dir(), i))
             {
                 return false;
             }
index 07d93a7..796a5a6 100644 (file)
@@ -24,6 +24,18 @@ struct probe_paths_t
     pal::string_t clrjit;
 };
 
+struct deps_resolved_asset_t
+{
+    deps_resolved_asset_t(const deps_asset_t& asset, const pal::string_t& resolved_path)
+        : asset(asset)
+        , resolved_path(resolved_path) { }
+
+    deps_asset_t asset;
+    pal::string_t resolved_path;
+};
+
+typedef std::unordered_map<pal::string_t, deps_resolved_asset_t> name_to_resolved_asset_map_t;
+
 class deps_resolver_t
 {
 public:
@@ -156,25 +168,22 @@ private:
     void get_dir_assemblies(
         const pal::string_t& dir,
         const pal::string_t& dir_name,
-        std::unordered_map<pal::string_t, pal::string_t>* dir_assemblies);
+        name_to_resolved_asset_map_t* items);
 
     // Probe entry in probe configurations and deps dir.
     bool probe_deps_entry(
         const deps_entry_t& entry,
         const pal::string_t& deps_dir,
+        int fx_level,
         pal::string_t* candidate);
 
     fx_definition_vector_t& m_fx_definitions;
 
     pal::string_t m_app_dir;
 
-    // Map of simple name -> full path of local/fx assemblies
-    typedef std::unordered_map<pal::string_t, pal::string_t> dir_assemblies_t;
-
     void add_tpa_asset(
-        const pal::string_t& asset_name,
-        const pal::string_t& asset_path,
-        dir_assemblies_t* items);
+        const deps_resolved_asset_t& asset,
+        name_to_resolved_asset_map_t* items);
 
     // The managed application the dependencies are being resolved for.
     pal::string_t m_managed_app;
index 81c2192..46ddc40 100644 (file)
@@ -43,6 +43,7 @@ set(SOURCES
     ../deps_format.cpp
     ../deps_entry.cpp
     ../fx_definition.cpp
+    ../version.cpp
 )
 
 
index 9044098..1a8274a 100644 (file)
@@ -2,9 +2,10 @@
 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
 
 #include "deps_format.h"
+#include "fx_definition.h"
+#include "fx_ver.h"
 #include "pal.h"
 #include "runtime_config.h"
-#include "fx_definition.h"
 
 fx_definition_t::fx_definition_t()
 {
@@ -40,3 +41,41 @@ void fx_definition_t::parse_deps(const deps_json_t::rid_fallback_graph_t& graph)
 {
     m_deps.parse(true, m_deps_file, graph);
 }
+
+bool fx_definition_t::did_minor_or_major_roll_forward_occur() const
+{
+    fx_ver_t requested_ver(-1, -1, -1);
+    if (!fx_ver_t::parse(m_requested_version, &requested_ver, false))
+    {
+        assert(false);
+        return false;
+    }
+
+    fx_ver_t found_ver(-1, -1, -1);
+    if (!fx_ver_t::parse(m_found_version, &found_ver, false))
+    {
+        assert(false);
+        return false;
+    }
+
+    if (requested_ver >= found_ver)
+    {
+        assert(requested_ver == found_ver); // We shouldn't have a > case here
+        return false;
+    }
+
+    if (requested_ver.get_major() != found_ver.get_major())
+    {
+        assert(requested_ver.get_major() < found_ver.get_major());
+        return true;
+    }
+
+    if (requested_ver.get_minor() != found_ver.get_minor())
+    {
+        assert(requested_ver.get_minor() < found_ver.get_minor());
+        return true;
+    }
+
+    // Differs in patch version only
+    return false;
+}
index dec0290..29f7e8d 100644 (file)
@@ -32,6 +32,8 @@ public:
     void parse_deps();
     void parse_deps(const deps_json_t::rid_fallback_graph_t& graph);
 
+    bool did_minor_or_major_roll_forward_occur() const;
+
 private:
     pal::string_t m_name;
     pal::string_t m_dir;
index 1038287..1e8ac30 100644 (file)
@@ -37,6 +37,7 @@ set(SOURCES
     ../json/casablanca/src/json/json_serialization.cpp
     ../json/casablanca/src/utilities/asyncrt_utils.cpp
     ../fx_definition.cpp
+    ../version.cpp
     ./hostfxr.cpp
     ./fx_ver.cpp
     ./fx_muxer.cpp
index fe297bf..8d9f19a 100644 (file)
@@ -3,6 +3,7 @@
 
 #include <cassert>
 #include "pal.h"
+#include "utils.h"
 #include "fx_ver.h"
 
 fx_ver_t::fx_ver_t(int major, int minor, int patch, const pal::string_t& pre, const pal::string_t& build)
@@ -118,20 +119,6 @@ int fx_ver_t::compare(const fx_ver_t&a, const fx_ver_t& b)
     return a.m_build.compare(b.m_build);
 }
 
-bool try_stou(const pal::string_t& str, unsigned* num)
-{
-    if (str.empty())
-    {
-        return false;
-    }
-    if (str.find_first_not_of(_X("0123456789")) != pal::string_t::npos)
-    {
-        return false;
-    }
-    *num = (unsigned) std::stoul(str);
-    return true;
-}
-
 bool parse_internal(const pal::string_t& ver, fx_ver_t* fx_ver, bool parse_only_production)
 {
     size_t maj_start = 0;
@@ -161,7 +148,7 @@ bool parse_internal(const pal::string_t& ver, fx_ver_t* fx_ver, bool parse_only_
 
     unsigned patch = 0;
     size_t pat_start = min_sep + 1;
-    size_t pat_sep = ver.find_first_not_of(_X("0123456789"), pat_start);
+    size_t pat_sep = index_of_non_numeric(ver, pat_start);
     if (pat_sep == pal::string_t::npos)
     {
         if (!try_stou(ver.substr(pat_start), &patch))
diff --git a/src/installer/corehost/cli/version.cpp b/src/installer/corehost/cli/version.cpp
new file mode 100644 (file)
index 0000000..59967b0
--- /dev/null
@@ -0,0 +1,169 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+#include <cassert>
+#include "pal.h"
+#include "version.h"
+
+// This type matches semantics of System.Version used by tooling and differs from fx_ver_t:
+// -- version_t does not require the third segment
+// -- version_t does not support the "pre" label behavior
+// -- if version_t is an empty value (-1 for all segments) then as_str() returns an empty string
+// -- Different terminology; fx_ver_t(major, minor, patch, build) vs version_t(major, minor, build, revision)
+
+version_t::version_t() : version_t(-1, -1, -1, -1) { }
+
+version_t::version_t(int major, int minor, int build, int revision)
+    : m_major(major)
+    , m_minor(minor)
+    , m_build(build)
+    , m_revision(revision) { }
+
+bool version_t::operator ==(const version_t& b) const
+{
+    return compare(*this, b) == 0;
+}
+
+bool version_t::operator !=(const version_t& b) const
+{
+    return !operator ==(b);
+}
+
+bool version_t::operator <(const version_t& b) const
+{
+    return compare(*this, b) < 0;
+}
+
+bool version_t::operator >(const version_t& b) const
+{
+    return compare(*this, b) > 0;
+}
+
+bool version_t::operator <=(const version_t& b) const
+{
+    return compare(*this, b) <= 0;
+}
+
+bool version_t::operator >=(const version_t& b) const
+{
+    return compare(*this, b) >= 0;
+}
+
+pal::string_t version_t::as_str() const
+{
+    pal::stringstream_t stream;
+
+    if (m_major >= 0)
+    {
+        stream << m_major;
+
+        if (m_minor >= 0)
+        {
+            stream << _X(".") << m_minor;
+
+            if (m_build >= 0)
+            {
+                stream << _X(".") << m_build;
+
+                if (m_revision >= 0)
+                {
+                    stream << _X(".") << m_revision;
+                }
+            }
+        }
+    }
+
+    return stream.str();
+}
+
+/*static*/ int version_t::compare(const version_t&a, const version_t& b)
+{
+    if (a.m_major != b.m_major)
+    {
+        return (a.m_major > b.m_major) ? 1 : -1;
+    }
+
+    if (a.m_minor != b.m_minor)
+    {
+        return (a.m_minor > b.m_minor) ? 1 : -1;
+    }
+
+    if (a.m_build != b.m_build)
+    {
+        return (a.m_build > b.m_build) ? 1 : -1;
+    }
+
+    if (a.m_revision != b.m_revision)
+    {
+        return (a.m_revision > b.m_revision) ? 1 : -1;
+    }
+
+    return 0;
+}
+
+bool parse_internal(const pal::string_t& ver, version_t* ver_out)
+{
+    unsigned major = -1;
+    size_t maj_start = 0;
+    size_t maj_sep = ver.find(_X('.'));
+    if (maj_sep == pal::string_t::npos)
+    {
+        return false; // minor required
+    }
+    if (!try_stou(ver.substr(maj_start, maj_sep), &major))
+    {
+        return false;
+    }
+
+    unsigned minor = -1;
+    size_t min_start = maj_sep + 1;
+    size_t min_sep = ver.find(_X('.'), min_start);
+    if (min_sep == pal::string_t::npos)
+    {
+        if (!try_stou(ver.substr(min_start), &minor))
+        {
+            return false;
+        }
+        *ver_out = version_t(major, minor, -1, -1);
+        return true; // build and revision not required
+    }
+    if (!try_stou(ver.substr(min_start, min_sep - min_start), &minor))
+    {
+        return false;
+    }
+
+    unsigned build = -1;
+    size_t build_start = min_sep + 1;
+    size_t build_sep = ver.find(_X('.'), build_start);
+    if (build_sep == pal::string_t::npos)
+    {
+        if (!try_stou(ver.substr(build_start), &build))
+        {
+            return false;
+        }
+        *ver_out = version_t(major, minor, build, -1);
+        return true; // revision not required
+    }
+    if (!try_stou(ver.substr(build_start, build_sep - build_start), &build))
+    {
+        return false;
+    }
+
+    unsigned revision = -1;
+    size_t revision_start = build_sep + 1;
+    if (!try_stou(ver.substr(revision_start), &revision))
+    {
+        return false;
+    }
+    *ver_out = version_t(major, minor, build, revision);
+
+    return true;
+}
+
+/* static */
+bool version_t::parse(const pal::string_t& ver, version_t* ver_out)
+{
+    bool valid = parse_internal(ver, ver_out);
+    assert(!valid || ver_out->as_str() == ver);
+    return valid;
+}
diff --git a/src/installer/corehost/cli/version.h b/src/installer/corehost/cli/version.h
new file mode 100644 (file)
index 0000000..c0bc57d
--- /dev/null
@@ -0,0 +1,45 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+#ifndef __VERSION_H__
+#define __VERSION_H__
+
+#include "pal.h"
+#include "utils.h"
+
+struct version_t
+{
+    version_t();
+    version_t(int major, int minor, int build, int revision);
+
+    int get_major() const { return m_major; }
+    int get_minor() const { return m_minor; }
+    int get_build() const { return m_build; }
+    int get_revision() const { return m_revision; }
+
+    void set_major(int m) { m_major = m; }
+    void set_minor(int m) { m_minor = m; }
+    void set_build(int m) { m_build = m; }
+    void set_revision(int m) { m_revision = m; }
+
+    pal::string_t as_str() const;
+
+    bool operator ==(const version_t& b) const;
+    bool operator !=(const version_t& b) const;
+    bool operator <(const version_t& b) const;
+    bool operator >(const version_t& b) const;
+    bool operator <=(const version_t& b) const;
+    bool operator >=(const version_t& b) const;
+
+    static bool parse(const pal::string_t& ver, version_t* ver_out);
+
+private:
+    int m_major;
+    int m_minor;
+    int m_build;
+    int m_revision;
+
+    static int compare(const version_t&a, const version_t& b);
+};
+
+#endif // __VERSION_H__
\ No newline at end of file
index 471bc75..1f04a69 100644 (file)
@@ -167,6 +167,23 @@ void replace_char(pal::string_t* path, pal::char_t match, pal::char_t repl)
     }
 }
 
+pal::string_t get_replaced_char(const pal::string_t& path, pal::char_t match, pal::char_t repl)
+{
+    int pos = path.find(match);
+    if (pos == pal::string_t::npos)
+    {
+        return path;
+    }
+
+    pal::string_t out = path;
+    do
+    {
+        out[pos] = repl;
+    } while ((pos = out.find(match, pos)) != pal::string_t::npos);
+
+    return out;
+}
+
 const pal::char_t* get_arch()
 {
 #if _TARGET_AMD64_
@@ -334,3 +351,22 @@ bool get_path_from_argv(pal::string_t *path)
 
     return false;
 }
+
+size_t index_of_non_numeric(const pal::string_t& str, unsigned i)
+{
+    return str.find_first_not_of(_X("0123456789"), i);
+}
+
+bool try_stou(const pal::string_t& str, unsigned* num)
+{
+    if (str.empty())
+    {
+        return false;
+    }
+    if (index_of_non_numeric(str, 0) != pal::string_t::npos)
+    {
+        return false;
+    }
+    *num = (unsigned)std::stoul(str);
+    return true;
+}
index bb56e5d..56a2552 100644 (file)
@@ -32,6 +32,7 @@ bool library_exists_in_dir(const pal::string_t& lib_dir, const pal::string_t& li
 bool coreclr_exists_in_dir(const pal::string_t& candidate);
 void remove_trailing_dir_seperator(pal::string_t* dir);
 void replace_char(pal::string_t* path, pal::char_t match, pal::char_t repl);
+pal::string_t get_replaced_char(const pal::string_t& path, pal::char_t match, pal::char_t repl);
 const pal::char_t* get_arch();
 pal::string_t get_last_known_arg(
     const opt_map_t& opts,
@@ -48,4 +49,6 @@ bool get_env_shared_store_dirs(std::vector<pal::string_t>* dirs, const pal::stri
 bool get_global_shared_store_dirs(std::vector<pal::string_t>* dirs, const pal::string_t& arch, const pal::string_t& tfm);
 bool multilevel_lookup_enabled();
 bool get_path_from_argv(pal::string_t *path);
+size_t index_of_non_numeric(const pal::string_t& str, unsigned i);
+bool try_stou(const pal::string_t& str, unsigned* num);
 #endif
index 6e3f943..0eb8620 100644 (file)
@@ -36,5 +36,22 @@ namespace System.Collections.Generic
                 .Where(a => string.Equals(a.Runtime, runtime, StringComparison.Ordinal))
                 .SelectMany(a => a.AssetPaths);
         }
+
+        public static IEnumerable<RuntimeFile> GetDefaultRuntimeFileAssets(this IEnumerable<RuntimeAssetGroup> self) => GetRuntimeFiles(self, string.Empty);
+        public static IEnumerable<RuntimeFile> GetRuntimeFileAssets(this IEnumerable<RuntimeAssetGroup> self, string runtime)
+        {
+            if (string.IsNullOrEmpty(runtime))
+            {
+                throw new ArgumentNullException(nameof(runtime));
+            }
+            return GetRuntimeFiles(self, runtime);
+        }
+
+        private static IEnumerable<RuntimeFile> GetRuntimeFiles(IEnumerable<RuntimeAssetGroup> groups, string runtime)
+        {
+            return groups
+                .Where(a => string.Equals(a.Runtime, runtime, StringComparison.Ordinal))
+                .SelectMany(a => a.RuntimeFiles);
+        }
     }
 }
index 59f73ea..7892292 100644 (file)
@@ -19,6 +19,15 @@ namespace Microsoft.Extensions.DependencyModel
             return self.RuntimeLibraries.SelectMany(library => library.GetDefaultNativeAssets(self));
         }
 
+        public static IEnumerable<RuntimeFile> GetDefaultNativeRuntimeFileAssets(this DependencyContext self)
+        {
+            if (self == null)
+            {
+                throw new ArgumentNullException(nameof(self));
+            }
+            return self.RuntimeLibraries.SelectMany(library => library.GetDefaultNativeRuntimeFileAssets(self));
+        }
+
         public static IEnumerable<string> GetRuntimeNativeAssets(this DependencyContext self, string runtimeIdentifier)
         {
             if (self == null)
@@ -32,6 +41,19 @@ namespace Microsoft.Extensions.DependencyModel
             return self.RuntimeLibraries.SelectMany(library => library.GetRuntimeNativeAssets(self, runtimeIdentifier));
         }
 
+        public static IEnumerable<RuntimeFile> GetRuntimeNativeRuntimeFileAssets(this DependencyContext self, string runtimeIdentifier)
+        {
+            if (self == null)
+            {
+                throw new ArgumentNullException(nameof(self));
+            }
+            if (runtimeIdentifier == null)
+            {
+                throw new ArgumentNullException(nameof(runtimeIdentifier));
+            }
+            return self.RuntimeLibraries.SelectMany(library => library.GetRuntimeNativeRuntimeFileAssets(self, runtimeIdentifier));
+        }
+
         public static IEnumerable<string> GetDefaultNativeAssets(this RuntimeLibrary self, DependencyContext context)
         {
             if (self == null)
@@ -41,6 +63,15 @@ namespace Microsoft.Extensions.DependencyModel
             return ResolveAssets(context, string.Empty, self.NativeLibraryGroups);
         }
 
+        public static IEnumerable<RuntimeFile> GetDefaultNativeRuntimeFileAssets(this RuntimeLibrary self, DependencyContext context)
+        {
+            if (self == null)
+            {
+                throw new ArgumentNullException(nameof(self));
+            }
+            return ResolveRuntimeFiles(context, string.Empty, self.NativeLibraryGroups);
+        }
+
         public static IEnumerable<string> GetRuntimeNativeAssets(this RuntimeLibrary self, DependencyContext context, string runtimeIdentifier)
         {
             if (self == null)
@@ -58,6 +89,23 @@ namespace Microsoft.Extensions.DependencyModel
             return ResolveAssets(context, runtimeIdentifier, self.NativeLibraryGroups);
         }
 
+        public static IEnumerable<RuntimeFile> GetRuntimeNativeRuntimeFileAssets(this RuntimeLibrary self, DependencyContext context, string runtimeIdentifier)
+        {
+            if (self == null)
+            {
+                throw new ArgumentNullException(nameof(self));
+            }
+            if (context == null)
+            {
+                throw new ArgumentNullException(nameof(context));
+            }
+            if (runtimeIdentifier == null)
+            {
+                throw new ArgumentNullException(nameof(runtimeIdentifier));
+            }
+            return ResolveRuntimeFiles(context, runtimeIdentifier, self.NativeLibraryGroups);
+        }
+
         public static IEnumerable<AssemblyName> GetDefaultAssemblyNames(this DependencyContext self)
         {
             if (self == null)
@@ -136,6 +184,16 @@ namespace Microsoft.Extensions.DependencyModel
             return SelectAssets(rids, assets);
         }
 
+        private static IEnumerable<RuntimeFile> ResolveRuntimeFiles(
+            DependencyContext context,
+            string runtimeIdentifier,
+            IEnumerable<RuntimeAssetGroup> assets)
+        {
+            var fallbacks = context.RuntimeGraph.FirstOrDefault(f => f.Runtime == runtimeIdentifier);
+            var rids = Enumerable.Concat(new[] { runtimeIdentifier }, fallbacks?.Fallbacks ?? Enumerable.Empty<string>());
+            return SelectRuntimeFiles(rids, assets);
+        }
+
         private static IEnumerable<string> SelectAssets(IEnumerable<string> rids, IEnumerable<RuntimeAssetGroup> groups)
         {
             foreach (var rid in rids)
@@ -151,5 +209,19 @@ namespace Microsoft.Extensions.DependencyModel
             return groups.GetDefaultAssets();
         }
 
+        private static IEnumerable<RuntimeFile> SelectRuntimeFiles(IEnumerable<string> rids, IEnumerable<RuntimeAssetGroup> groups)
+        {
+            foreach (var rid in rids)
+            {
+                var group = groups.FirstOrDefault(g => g.Runtime == rid);
+                if (group != null)
+                {
+                    return group.RuntimeFiles;
+                }
+            }
+
+            // Return the RID-agnostic group
+            return groups.GetDefaultRuntimeFileAssets();
+        }
     }
 }
index d0b23c9..d84abe0 100644 (file)
@@ -304,8 +304,8 @@ namespace Microsoft.Extensions.DependencyModel
         private TargetLibrary ReadTargetLibrary(JsonTextReader reader, string targetLibraryName)
         {
             IEnumerable<Dependency> dependencies = null;
-            List<string> runtimes = null;
-            List<string> natives = null;
+            List<RuntimeFile> runtimes = null;
+            List<RuntimeFile> natives = null;
             List<string> compilations = null;
             List<RuntimeTargetEntryStub> runtimeTargets = null;
             List<ResourceAssembly> resources = null;
@@ -321,10 +321,10 @@ namespace Microsoft.Extensions.DependencyModel
                         dependencies = ReadTargetLibraryDependencies(reader);
                         break;
                     case DependencyContextStrings.RuntimeAssembliesKey:
-                        runtimes = ReadPropertyNames(reader);
+                        runtimes = ReadRuntimeFiles(reader);
                         break;
                     case DependencyContextStrings.NativeLibrariesKey:
-                        natives = ReadPropertyNames(reader);
+                        natives = ReadRuntimeFiles(reader);
                         break;
                     case DependencyContextStrings.CompileTimeAssembliesKey:
                         compilations = ReadPropertyNames(reader);
@@ -398,6 +398,46 @@ namespace Microsoft.Extensions.DependencyModel
             return runtimes;
         }
 
+        private List<RuntimeFile> ReadRuntimeFiles(JsonTextReader reader)
+        {
+            var runtimeFiles = new List<RuntimeFile>();
+
+            reader.ReadStartObject();
+
+            while (reader.Read() && reader.TokenType == JsonToken.PropertyName)
+            {
+                string assemblyVersion = null;
+                string fileVersion = null;
+
+                var path = (string)reader.Value;
+
+                reader.ReadStartObject();
+
+                string propertyName;
+                string propertyValue;
+                while (reader.TryReadStringProperty(out propertyName, out propertyValue))
+                {
+                    switch (propertyName)
+                    {
+                        case DependencyContextStrings.AssemblyVersionPropertyName:
+                            assemblyVersion = propertyValue;
+                            break;
+                        case DependencyContextStrings.FileVersionPropertyName:
+                            fileVersion = propertyValue;
+                            break;
+                    }
+                }
+
+                reader.CheckEndObject();
+
+                runtimeFiles.Add(new RuntimeFile(path, assemblyVersion, fileVersion));
+            }
+
+            reader.CheckEndObject();
+
+            return runtimeFiles;
+        }
+
         private List<RuntimeTargetEntryStub> ReadTargetLibraryRuntimeTargets(JsonTextReader reader)
         {
             var runtimeTargets = new List<RuntimeTargetEntryStub>();
@@ -423,6 +463,12 @@ namespace Microsoft.Extensions.DependencyModel
                         case DependencyContextStrings.AssetTypePropertyName:
                             runtimeTarget.Type = Pool(propertyValue);
                             break;
+                        case DependencyContextStrings.AssemblyVersionPropertyName:
+                            runtimeTarget.AssemblyVersion = propertyValue;
+                            break;
+                        case DependencyContextStrings.FileVersionPropertyName:
+                            runtimeTarget.FileVersion = propertyValue;
+                            break;
                     }
                 }
 
@@ -605,26 +651,26 @@ namespace Microsoft.Extensions.DependencyModel
                     {
                         var groupRuntimeAssemblies = ridGroup
                             .Where(e => e.Type == DependencyContextStrings.RuntimeAssetType)
-                            .Select(e => e.Path)
+                            .Select(e => new RuntimeFile(e.Path, e.AssemblyVersion, e.FileVersion))
                             .ToArray();
 
                         if (groupRuntimeAssemblies.Any())
                         {
                             runtimeAssemblyGroups.Add(new RuntimeAssetGroup(
                                 ridGroup.Key,
-                                groupRuntimeAssemblies.Where(a => Path.GetFileName(a) != "_._")));
+                                groupRuntimeAssemblies.Where(a => Path.GetFileName(a.Path) != "_._")));
                         }
 
                         var groupNativeLibraries = ridGroup
                             .Where(e => e.Type == DependencyContextStrings.NativeAssetType)
-                            .Select(e => e.Path)
+                            .Select(e => new RuntimeFile(e.Path, e.AssemblyVersion, e.FileVersion))
                             .ToArray();
 
                         if (groupNativeLibraries.Any())
                         {
                             nativeLibraryGroups.Add(new RuntimeAssetGroup(
                                 ridGroup.Key,
-                                groupNativeLibraries.Where(a => Path.GetFileName(a) != "_._")));
+                                groupNativeLibraries.Where(a => Path.GetFileName(a.Path) != "_._")));
                         }
                     }
                 }
@@ -698,9 +744,9 @@ namespace Microsoft.Extensions.DependencyModel
 
             public IEnumerable<Dependency> Dependencies;
 
-            public List<string> Runtimes;
+            public List<RuntimeFile> Runtimes;
 
-            public List<string> Natives;
+            public List<RuntimeFile> Natives;
 
             public List<string> Compilations;
 
@@ -718,6 +764,10 @@ namespace Microsoft.Extensions.DependencyModel
             public string Path;
 
             public string Rid;
+
+            public string AssemblyVersion;
+
+            public string FileVersion;
         }
 
         private struct LibraryStub
index 021a89d..ffe041c 100644 (file)
@@ -82,5 +82,9 @@ namespace Microsoft.Extensions.DependencyModel
         internal const string LocalePropertyName = "locale";
 
         internal const string CompilationOnlyPropertyName = "compileOnly";
+
+        internal const string AssemblyVersionPropertyName = "assemblyVersion";
+
+        internal const string FileVersionPropertyName = "fileVersion";
     }
 }
\ No newline at end of file
index 2c6a9c5..9801634 100644 (file)
@@ -170,12 +170,12 @@ namespace Microsoft.Extensions.DependencyModel
 
         private void AddAssets(JObject libraryObject, string key, RuntimeAssetGroup group)
         {
-            if (group == null || !group.AssetPaths.Any())
+            if (group == null || !group.RuntimeFiles.Any())
             {
                 return;
             }
             libraryObject.Add(new JProperty(key,
-                       WriteAssetList(group.AssetPaths))
+                       WriteAssetList(group.RuntimeFiles))
                    );
         }
 
@@ -275,9 +275,9 @@ namespace Microsoft.Extensions.DependencyModel
         {
             foreach (var group in assetGroups.Where(g => !string.IsNullOrEmpty(g.Runtime)))
             {
-                if (group.AssetPaths.Any())
+                if (group.RuntimeFiles.Any())
                 {
-                    AddRuntimeSpecificAssets(runtimeTargets, group.AssetPaths, group.Runtime, assetType);
+                    AddRuntimeSpecificAssets(runtimeTargets, group.RuntimeFiles, group.Runtime, assetType);
                 }
                 else
                 {
@@ -294,16 +294,26 @@ namespace Microsoft.Extensions.DependencyModel
             }
         }
 
-        private void AddRuntimeSpecificAssets(JObject target, IEnumerable<string> assets, string runtime, string assetType)
+        private void AddRuntimeSpecificAssets(JObject target, IEnumerable<RuntimeFile> assets, string runtime, string assetType)
         {
             foreach (var asset in assets)
             {
-                target.Add(new JProperty(NormalizePath(asset),
-                    new JObject(
+                var asset_props = new JObject(
                         new JProperty(DependencyContextStrings.RidPropertyName, runtime),
                         new JProperty(DependencyContextStrings.AssetTypePropertyName, assetType)
-                        )
-                    ));
+                        );
+
+                if (asset.AssemblyVersion != null)
+                {
+                    asset_props.Add(DependencyContextStrings.AssemblyVersionPropertyName, asset.AssemblyVersion);
+                }
+
+                if (asset.FileVersion != null)
+                {
+                    asset_props.Add(DependencyContextStrings.FileVersionPropertyName, asset.FileVersion);
+                }
+
+                target.Add(new JProperty(NormalizePath(asset.Path), asset_props));
             }
         }
 
@@ -312,6 +322,29 @@ namespace Microsoft.Extensions.DependencyModel
             return new JObject(assetPaths.Select(assembly => new JProperty(NormalizePath(assembly), new JObject())));
         }
 
+        private JObject WriteAssetList(IEnumerable<RuntimeFile> runtimeFiles)
+        {
+            var target = new JObject();
+            foreach (var runtimeFile in runtimeFiles)
+            {
+                var fileJson = new JObject();
+
+                if (runtimeFile.AssemblyVersion != null)
+                {
+                    fileJson.Add(DependencyContextStrings.AssemblyVersionPropertyName, runtimeFile.AssemblyVersion);
+                }
+
+                if (runtimeFile.FileVersion != null)
+                {
+                    fileJson.Add(DependencyContextStrings.FileVersionPropertyName, runtimeFile.FileVersion);
+                }
+
+                target.Add(new JProperty(NormalizePath(runtimeFile.Path), fileJson));
+            }
+
+            return target;
+        }
+
         private JObject WriteLibraries(DependencyContext context)
         {
             var allLibraries =
index d9eee3e..3d83305 100644 (file)
@@ -8,12 +8,21 @@ namespace Microsoft.Extensions.DependencyModel
 {
     public class RuntimeAssetGroup
     {
+        private IReadOnlyList<string> _assetPaths;
+        private IReadOnlyList<RuntimeFile> _runtimeFiles;
+
         public RuntimeAssetGroup(string runtime, params string[] assetPaths) : this(runtime, (IEnumerable<string>)assetPaths) { }
 
         public RuntimeAssetGroup(string runtime, IEnumerable<string> assetPaths)
         {
             Runtime = runtime;
-            AssetPaths = assetPaths.ToArray();
+            _assetPaths = assetPaths.ToArray();
+        }
+
+        public RuntimeAssetGroup(string runtime, IEnumerable<RuntimeFile> runtimeFiles)
+        {
+            Runtime = runtime;
+            _runtimeFiles = runtimeFiles.ToArray();
         }
 
         /// <summary>
@@ -22,8 +31,35 @@ namespace Microsoft.Extensions.DependencyModel
         public string Runtime { get; }
 
         /// <summary>
-        /// Gets a list of assets provided in this runtime group
+        /// Gets a list of asset paths provided in this runtime group
+        /// </summary>
+        public IReadOnlyList<string> AssetPaths
+        {
+            get
+            {
+                if (_assetPaths != null)
+                {
+                    return _assetPaths;
+                }
+
+                return _runtimeFiles.Select(file => file.Path).ToArray();
+            }
+        }
+
+        /// <summary>
+        /// Gets a list of RuntimeFiles provided in this runtime group
         /// </summary>
-        public IReadOnlyList<string> AssetPaths { get; }
+        public IReadOnlyList<RuntimeFile> RuntimeFiles
+        {
+            get
+            {
+                if (_runtimeFiles != null)
+                {
+                    return _runtimeFiles;
+                }
+
+                return _assetPaths.Select(path => new RuntimeFile(path, null, null)).ToArray();
+            }
+        }
     }
 }
\ No newline at end of file
diff --git a/src/installer/managed/Microsoft.Extensions.DependencyModel/RuntimeFile.cs b/src/installer/managed/Microsoft.Extensions.DependencyModel/RuntimeFile.cs
new file mode 100644 (file)
index 0000000..1660530
--- /dev/null
@@ -0,0 +1,28 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+
+namespace Microsoft.Extensions.DependencyModel
+{
+    public class RuntimeFile
+    {
+        public RuntimeFile(string path, string assemblyVersion, string fileVersion)
+        {
+            if (string.IsNullOrEmpty(path))
+            {
+                throw new ArgumentException(nameof(path));
+            }
+
+            Path = path;
+            AssemblyVersion = assemblyVersion;
+            FileVersion = fileVersion;
+        }
+
+        public string Path { get; }
+
+        public string AssemblyVersion { get; }
+
+        public string FileVersion { get; }
+    }
+}
index 783be9e..63f254b 100644 (file)
@@ -102,7 +102,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku
 
             // The uber framework is a copy of the base framework, minus a few files
             _builtSharedUberFxDir = Path.Combine(_builtDotnet, "shared", "Microsoft.UberFramework", _sharedFxVersion);
-            CreateUberFrameworkArtifacts(_builtSharedFxDir, _builtSharedUberFxDir);
+            CreateUberFrameworkArtifacts(_builtSharedFxDir, _builtSharedUberFxDir, "1.0.1.2", "1.2.3.4");
 
             _hostPolicyDllName = Path.GetFileName(fixture.TestProject.HostPolicyDll);
 
@@ -823,6 +823,12 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku
 
             // The System.Collections.Immutable.dll is located in the UberFramework and NetCoreApp
             // The System.Collections.dll is only located in NetCoreApp
+            // Version: NetCoreApp 9999.0.0
+            //          UberFramework 7777.0.0
+            // Exe: NetCoreApp 9999.0.0
+            //      UberFramework 7777.0.0
+            // Expected: 9999.0.0
+            //           7777.0.0
             dotnet.Exec(appDll)
                 .WorkingDirectory(_currentWorkingDir)
                 .EnvironmentVariable("COREHOST_TRACE", "1")
@@ -834,12 +840,98 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku
                 .And
                 .HaveStdErrContaining(Path.Combine("7777.0.0", "System.Collections.Immutable.dll"))
                 .And
-                .HaveStdErrContaining(Path.Combine("9999.0.0", "System.Collections.dll"));
+                .HaveStdErrContaining(Path.Combine("9999.0.0", "System.Collections.dll"))
+                .And
+                .NotHaveStdErrContaining(Path.Combine("9999.0.0", "System.Collections.Immutable.dll"));
 
             DeleteAvailableSharedFxVersions(_exeSharedFxBaseDir, "9999.0.0");
             DeleteAvailableSharedFxVersions(_exeSharedUberFxBaseDir, "7777.0.0");
         }
 
+        /* This test will be added once the SDK write the assemblyVersion and fileVersion properties. Verified manually.
+        [Fact]
+        public void Multiple_SharedFxLookup_NetCoreApp_MinorRollForward_Wins_Over_UberFx()
+        {
+            var fixture = PreviouslyBuiltAndRestoredPortableTestProjectFixture
+                .Copy();
+
+            var dotnet = fixture.BuiltDotnet;
+            var appDll = fixture.TestProject.AppDll;
+
+            string runtimeConfig = Path.Combine(fixture.TestProject.OutputDirectory, "SharedFxLookupPortableApp.runtimeconfig.json");
+            SetRuntimeConfigJson(runtimeConfig, "7777.0.0", null, useUberFramework: true);
+
+            // Add versions in the exe folders
+            AddAvailableSharedFxVersions(_exeSharedFxBaseDir, "9999.1.0");
+            AddAvailableSharedUberFxVersions(_exeSharedUberFxBaseDir, "9999.0.0", null, "7777.0.0");
+
+            string uberFile = Path.Combine(_exeSharedUberFxBaseDir, "7777.0.0", "System.Collections.Immutable.dll");
+            string netCoreAppFile = Path.Combine(_exeSharedFxBaseDir, "9999.1.0", "System.Collections.Immutable.dll");
+            // The System.Collections.Immutable.dll is located in the UberFramework and NetCoreApp
+            // Version: NetCoreApp 9999.0.0
+            //          UberFramework 7777.0.0
+            //          'Roll forward on no candidate fx' enabled through config
+            // Exe: NetCoreApp 9999.1.0
+            //      UberFramework 7777.0.0
+            // Expected: 9999.1.0
+            //           7777.0.0
+            dotnet.Exec(appDll)
+                .WorkingDirectory(_currentWorkingDir)
+                .EnvironmentVariable("COREHOST_TRACE", "1")
+                .CaptureStdOut()
+                .CaptureStdErr()
+                .Execute()
+                .Should()
+                .Pass()
+                .And
+                .HaveStdErrContaining($"Replacing deps entry [{uberFile}, AssemblyVersion:1.0.1.2, FileVersion:1.2.3.4] with [{netCoreAppFile}");
+
+            DeleteAvailableSharedFxVersions(_exeSharedFxBaseDir, "9999.1.0");
+            DeleteAvailableSharedFxVersions(_exeSharedUberFxBaseDir, "7777.0.0");
+        }
+        */
+
+        [Fact]
+        public void Multiple_SharedFxLookup_Uber_Wins_Over_NetCoreApp_On_PatchRollForward()
+        {
+            var fixture = PreviouslyBuiltAndRestoredPortableTestProjectFixture
+                .Copy();
+
+            var dotnet = fixture.BuiltDotnet;
+            var appDll = fixture.TestProject.AppDll;
+
+            string runtimeConfig = Path.Combine(fixture.TestProject.OutputDirectory, "SharedFxLookupPortableApp.runtimeconfig.json");
+            SetRuntimeConfigJson(runtimeConfig, "7777.0.0", null, useUberFramework: true);
+
+            // Add versions in the exe folders
+            AddAvailableSharedFxVersions(_exeSharedFxBaseDir, "9999.0.1");
+            AddAvailableSharedUberFxVersions(_exeSharedUberFxBaseDir, "9999.0.0", null, "7777.0.0");
+
+            // The System.Collections.Immutable.dll is located in the UberFramework and NetCoreApp
+            // Version: NetCoreApp 9999.0.0
+            //          UberFramework 7777.0.0
+            //          'Roll forward on no candidate fx' enabled through config
+            // Exe: NetCoreApp 9999.0.1
+            //      UberFramework 7777.0.0
+            // Expected: 9999.0.1
+            //           7777.0.0
+            dotnet.Exec(appDll)
+                .WorkingDirectory(_currentWorkingDir)
+                .EnvironmentVariable("COREHOST_TRACE", "1")
+                .CaptureStdOut()
+                .CaptureStdErr()
+                .Execute()
+                .Should()
+                .Pass()
+                .And
+                .HaveStdErrContaining(Path.Combine("7777.0.0", "System.Collections.Immutable.dll"))
+                .And
+                .NotHaveStdErrContaining(Path.Combine("9999.1.0", "System.Collections.Immutable.dll"));
+
+            DeleteAvailableSharedFxVersions(_exeSharedFxBaseDir, "9999.0.1");
+            DeleteAvailableSharedFxVersions(_exeSharedUberFxBaseDir, "7777.0.0");
+        }
+
         // This method adds a list of new framework version folders in the specified
         // sharedFxBaseDir. The files are copied from the _buildSharedFxDir.
         // Remarks:
@@ -1018,7 +1110,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku
             File.WriteAllText(destFile, json.ToString());
         }
 
-        static private void CreateUberFrameworkArtifacts(string builtSharedFxDir, string builtSharedUberFxDir)
+        static private void CreateUberFrameworkArtifacts(string builtSharedFxDir, string builtSharedUberFxDir, string assemblyVersion = null, string fileVersion = null)
         {
             DirectoryInfo dir = new DirectoryInfo(builtSharedUberFxDir);
             if (dir.Exists)
@@ -1029,6 +1121,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku
             dir.Create();
 
             string fxName = "UberFx";
+            string testPackage = "System.Collections.Immutable/1.0.0";
             string testAssembly = "System.Collections.Immutable";
 
             // Create the deps.json. Generated file:
@@ -1039,7 +1132,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku
                   },
                   "targets": {
                     "UberFx": {
-                      "System.Collections.Immutable": {
+                      "System.Collections.Immutable/1.0.0": {
                         "runtime": {
                           "System.Collections.Immutable.dll": {}
                         }
@@ -1047,7 +1140,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku
                     }
                   },
                   "libraries": {
-                    "System.Collections.Immutable": {
+                    "System.Collections.Immutable/1.0.0": {
                       "type": "assemblyreference",
                       "serviceable": false,
                       "sha512": ""
@@ -1055,6 +1148,17 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku
                   }
                 }
              */
+            JObject versionInfo = new JObject();
+            if (assemblyVersion != null)
+            {
+                versionInfo.Add(new JProperty("assemblyVersion", assemblyVersion));
+            }
+
+            if (fileVersion != null)
+            {
+                versionInfo.Add(new JProperty("fileVersion", fileVersion));
+            }
+
             JObject depsjson = new JObject(
                 new JProperty("runtimeTarget",
                     new JObject(
@@ -1065,12 +1169,12 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku
                     new JObject(
                       new JProperty(fxName,
                           new JObject(
-                              new JProperty(testAssembly,
+                              new JProperty(testPackage,
                                   new JObject(
                                       new JProperty("runtime",
                                           new JObject(
                                               new JProperty(testAssembly + ".dll",
-                                                  new JObject()
+                                                  versionInfo
                                               )
                                           )
                                       )
@@ -1082,7 +1186,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku
               ),
                   new JProperty("libraries",
                       new JObject(
-                          new JProperty(testAssembly,
+                          new JProperty(testPackage,
                             new JObject(
                                 new JProperty("type", "assemblyreference"),
                                 new JProperty("serviceable", false),
index 5bcf133..2f951ab 100644 (file)
@@ -63,7 +63,7 @@ namespace Microsoft.Extensions.DependencyModel.Tests
         [Fact]
         public void GroupsRuntimeAssets()
         {
-            var context = Read(@"
+            string json = @"
  {
      ""targets"": {
          "".NETStandard,Version=v1.5"": {
@@ -85,14 +85,58 @@ namespace Microsoft.Extensions.DependencyModel.Tests
              ""sha512"": ""HASH-System.Banana""
          },
      }
- }");
+ }";
+
+            ReadGroupsRuntimeAssets(json);
+        }
+
+        [Fact]
+        public void GroupsRuntimeAssetsWithAssemblyVersions()
+        {
+            string json = @"
+ {
+     ""targets"": {
+         "".NETStandard,Version=v1.5"": {
+             ""System.Banana/1.0.0"": {
+                 ""runtimeTargets"": {
+                     ""runtimes/unix/Banana.dll"": { ""rid"": ""unix"", ""assetType"": ""runtime"", ""assemblyVersion"": ""1.2.3"", ""fileVersion"": ""4.5.6"" },
+                     ""runtimes/win7/Banana.dll"": { ""rid"": ""win7"",  ""assetType"": ""runtime"", ""fileVersion"": ""1.2.3""},
+
+                     ""runtimes/native/win7/Apple.dll"": { ""rid"": ""win7"",  ""assetType"": ""native"" },
+                     ""runtimes/native/unix/libapple.so"": { ""rid"": ""unix"",  ""assetType"": ""native"" }
+                 }
+             }
+         }
+     },
+     ""libraries"": {
+         ""System.Banana/1.0.0"": {
+             ""type"": ""package"",
+             ""serviceable"": false,
+             ""sha512"": ""HASH-System.Banana""
+         },
+     }
+ }";
+
+            RuntimeLibrary runtimeLib = ReadGroupsRuntimeAssets(json);
+            runtimeLib.RuntimeAssemblyGroups.GetRuntimeFileAssets("unix").Single().AssemblyVersion.Should().Be("1.2.3");
+            runtimeLib.RuntimeAssemblyGroups.GetRuntimeFileAssets("unix").Single().FileVersion.Should().Be("4.5.6");
+            runtimeLib.RuntimeAssemblyGroups.GetRuntimeFileAssets("win7").Single().AssemblyVersion.Should().BeNull();
+            runtimeLib.RuntimeAssemblyGroups.GetRuntimeFileAssets("win7").Single().FileVersion.Should().Be("1.2.3");
+        }
+
+        private RuntimeLibrary ReadGroupsRuntimeAssets(string json)
+        {
+            var context = Read(json);
+
             context.RuntimeLibraries.Should().HaveCount(1);
-            var runtimeLib = context.RuntimeLibraries.Single();
+            RuntimeLibrary runtimeLib = context.RuntimeLibraries.Single();
             runtimeLib.RuntimeAssemblyGroups.Should().HaveCount(2);
             runtimeLib.RuntimeAssemblyGroups.All(g => g.AssetPaths.Count == 1).Should().BeTrue();
 
             runtimeLib.NativeLibraryGroups.Should().HaveCount(2);
             runtimeLib.NativeLibraryGroups.All(g => g.AssetPaths.Count == 1).Should().BeTrue();
+
+            return runtimeLib;
         }
 
         [Fact]
@@ -402,7 +446,18 @@ namespace Microsoft.Extensions.DependencyModel.Tests
         [Fact]
         public void ReadsRuntimeLibrariesWithSubtargetsFromMainTargetForPortable()
         {
-            var context = Read(
+            ReadsRuntimeLibrariesWithSubtargetsFromMainTargetForPortable(false);
+        }
+
+        [Fact]
+        public void ReadsRuntimeLibrariesWithSubtargetsFromMainTargetForPortableWithAssemblyVersions()
+        {
+            ReadsRuntimeLibrariesWithSubtargetsFromMainTargetForPortable(true);
+        }
+
+        private void ReadsRuntimeLibrariesWithSubtargetsFromMainTargetForPortable(bool useAssemblyVersions)
+        {
+            string json =
 @"{
     ""runtimeTarget"": {
         ""name"": "".NETCoreApp,Version=v1.0""
@@ -422,8 +477,22 @@ namespace Microsoft.Extensions.DependencyModel.Tests
                     ""System.Foo"": ""1.0.0""
                 },
                 ""runtime"": {
-                    ""lib/dotnet5.4/System.Banana.dll"": { }
-                },
+                    ""lib/dotnet5.4/System.Banana.dll"": {";
+
+            if (useAssemblyVersions)
+            {
+                json +=
+@"                            ""assemblyVersion"": ""1.2.3"",
+                            ""fileVersion"": ""7.8.9""
+                    }";
+            }
+            else
+            {
+                json += " }";
+            }
+
+            json +=
+@"                },
                 ""runtimeTargets"": {
                     ""lib/win7/System.Banana.dll"": { ""assetType"": ""runtime"", ""rid"": ""win7-x64""},
                     ""lib/win7/Banana.dll"": { ""assetType"": ""native"", ""rid"": ""win7-x64""}
@@ -447,14 +516,15 @@ namespace Microsoft.Extensions.DependencyModel.Tests
             ""runtimeStoreManifestName"": ""placeHolderManifest.xml""
         },
     }
-}");
+}";
+            var context = Read(json);
+
             context.CompileLibraries.Should().HaveCount(2);
             var project = context.RuntimeLibraries.Should().Contain(l => l.Name == "MyApp").Subject;
             project.Version.Should().Be("1.0.1");
             project.RuntimeAssemblyGroups.GetDefaultAssets().Should().Contain("MyApp.dll");
             project.Type.Should().Be("project");
 
-
             var package = context.RuntimeLibraries.Should().Contain(l => l.Name == "System.Banana").Subject;
             package.Version.Should().Be("1.0.0");
             package.Hash.Should().Be("HASH-System.Banana");
@@ -466,7 +536,20 @@ namespace Microsoft.Extensions.DependencyModel.Tests
             package.ResourceAssemblies.Should().Contain(a => a.Path == "System.Banana.resources.dll")
                 .Subject.Locale.Should().Be("en-US");
 
-            package.RuntimeAssemblyGroups.GetDefaultAssets().Should().Contain("lib/dotnet5.4/System.Banana.dll");
+            if (useAssemblyVersions)
+            {
+                var assets = package.RuntimeAssemblyGroups.GetDefaultRuntimeFileAssets();
+                assets.Should().HaveCount(1);
+                var runtimeFile = assets.First();
+                runtimeFile.Path.Should().Be("lib/dotnet5.4/System.Banana.dll");
+                runtimeFile.AssemblyVersion.Should().Be("1.2.3");
+                runtimeFile.FileVersion.Should().Be("7.8.9");
+            }
+            else
+            {
+                package.RuntimeAssemblyGroups.GetDefaultAssets().Should().Contain("lib/dotnet5.4/System.Banana.dll");
+            }
+
             package.RuntimeAssemblyGroups.GetRuntimeAssets("win7-x64").Should().Contain("lib/win7/System.Banana.dll");
             package.NativeLibraryGroups.GetRuntimeAssets("win7-x64").Should().Contain("lib/win7/Banana.dll");
         }
index 9947646..4fbe46f 100644 (file)
@@ -192,6 +192,23 @@ namespace Microsoft.Extensions.DependencyModel.Tests
         [Fact]
         public void WritesRuntimeLibrariesToRuntimeTarget()
         {
+            var group = new RuntimeAssetGroup("win7-x64", "Banana.Win7-x64.dll");
+            WritesRuntimeLibrariesToRuntimeTarget(group);
+        }
+
+        [Fact]
+        public void WritesRuntimeLibrariesToRuntimeTargetWithAssemblyVersions()
+        {
+            RuntimeFile[] runtimeFile = { new RuntimeFile("Banana.Win7-x64.dll", "1.2.3", "7.8.9") };
+            var group = new RuntimeAssetGroup("win7-x64", runtimeFile);
+
+            var runtimeAssembly = WritesRuntimeLibrariesToRuntimeTarget(group);
+            runtimeAssembly.Should().HavePropertyValue("assemblyVersion", "1.2.3");
+            runtimeAssembly.Should().HavePropertyValue("fileVersion", "7.8.9");
+        }
+
+        private JObject WritesRuntimeLibrariesToRuntimeTarget(RuntimeAssetGroup group)
+        {
             var result = Save(Create(
                             "Target",
                             "runtime",
@@ -205,7 +222,7 @@ namespace Microsoft.Extensions.DependencyModel.Tests
                                         "HASH",
                                         new [] {
                                             new RuntimeAssetGroup(string.Empty, "Banana.dll"),
-                                            new RuntimeAssetGroup("win7-x64", "Banana.Win7-x64.dll")
+                                            group
                                         },
                                         new [] {
                                             new RuntimeAssetGroup(string.Empty, "runtimes\\linux\\native\\native.so"),
@@ -257,6 +274,8 @@ namespace Microsoft.Extensions.DependencyModel.Tests
             library.Should().HavePropertyValue("path", "PackagePath");
             library.Should().HavePropertyValue("hashPath", "PackageHashPath");
             library.Should().HavePropertyValue("runtimeStoreManifestName", "placeHolderManifest.xml");
+
+            return runtimeAssembly;
         }
 
         [Fact]
@@ -345,8 +364,30 @@ namespace Microsoft.Extensions.DependencyModel.Tests
         }
 
         [Fact]
+        public void WritesRuntimeTargetForNonPortableLegacy()
+        {
+            var group = new RuntimeAssetGroup(string.Empty, "Banana.dll");
+            var assetGroup = WritesRuntimeTarget(group);
+
+            var files = assetGroup.Should().HavePropertyAsObject("runtime").Subject;
+            files.Should().HaveProperty("Banana.dll");
+        }
+
+        [Fact]
         public void WritesRuntimeTargetForNonPortable()
         {
+            RuntimeFile[] runtimeFiles = { new RuntimeFile("Banana.dll", "1.2.3", "7.8.9") };
+            var group = new RuntimeAssetGroup(string.Empty, runtimeFiles);
+            var assetGroup = WritesRuntimeTarget(group);
+
+            var files = assetGroup.Should().HavePropertyAsObject("runtime").Subject;
+            var file = files.Should().HavePropertyAsObject("Banana.dll").Subject;
+            file.Should().HavePropertyValue("assemblyVersion", "1.2.3");
+            file.Should().HavePropertyValue("fileVersion", "7.8.9");
+        }
+
+        private JObject WritesRuntimeTarget(RuntimeAssetGroup group)
+        {
             var result = Save(Create(
                             "Target",
                             "runtime",
@@ -359,7 +400,7 @@ namespace Microsoft.Extensions.DependencyModel.Tests
                                         "1.2.3",
                                         "HASH",
                                         new [] {
-                                            new RuntimeAssetGroup(string.Empty, "Banana.dll")
+                                            group
                                         },
                                         new [] {
                                             new RuntimeAssetGroup(string.Empty, "runtimes\\osx\\native\\native.dylib")
@@ -377,22 +418,22 @@ namespace Microsoft.Extensions.DependencyModel.Tests
             // targets
             var targets = result.Should().HavePropertyAsObject("targets").Subject;
             var target = targets.Should().HavePropertyAsObject("Target/runtime").Subject;
-            var library = target.Should().HavePropertyAsObject("PackageName/1.2.3").Subject;
-            var dependencies = library.Should().HavePropertyAsObject("dependencies").Subject;
+            var assetGroup = target.Should().HavePropertyAsObject("PackageName/1.2.3").Subject;
+            var dependencies = assetGroup.Should().HavePropertyAsObject("dependencies").Subject;
             dependencies.Should().HavePropertyValue("Fruits.Abstract.dll", "2.0.0");
-            library.Should().HavePropertyAsObject("runtime")
-                .Subject.Should().HaveProperty("Banana.dll");
-            library.Should().HavePropertyAsObject("native")
+            assetGroup.Should().HavePropertyAsObject("native")
                 .Subject.Should().HaveProperty("runtimes/osx/native/native.dylib");
 
             //libraries
             var libraries = result.Should().HavePropertyAsObject("libraries").Subject;
-            library = libraries.Should().HavePropertyAsObject("PackageName/1.2.3").Subject;
+            var library = libraries.Should().HavePropertyAsObject("PackageName/1.2.3").Subject;
             library.Should().HavePropertyValue("sha512", "HASH");
             library.Should().HavePropertyValue("type", "package");
             library.Should().HavePropertyValue("serviceable", true);
             library.Should().HavePropertyValue("path", "PackagePath");
             library.Should().HavePropertyValue("hashPath", "PackageHashPath");
+
+            return assetGroup;
         }
 
         [Fact]
index 214d0c2..ccc49e3 100644 (file)
@@ -31,7 +31,7 @@ namespace Microsoft.Extensions.DependencyModel.Tests
             var token = Subject[expected];
             Execute.Assertion
                 .ForCondition(token != null)
-                .FailWith($"Expected {Subject} to have property '" + expected + "'");
+                .FailWith("Expected {0} to have property '{1}'", Subject, expected);
 
             return new AndWhichConstraint<JsonAssetions, JToken>(this, token);
         }
@@ -41,7 +41,7 @@ namespace Microsoft.Extensions.DependencyModel.Tests
             var token = Subject[expected];
             Execute.Assertion
                 .ForCondition(token == null)
-                .FailWith($"Expected {Subject} not to have property '" + expected + "'");
+                .FailWith("Expected {0} to have property '{1}'", Subject, expected);
 
             return new AndConstraint<JsonAssetions>(this);
         }