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;
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)
!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);
}
};
// 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);
{
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);
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);
#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
{
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;
// 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_
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;
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);
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;
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());
}
}
}
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);
}
}
}
{
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);
}
}
}
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;
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;
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);
{
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; };
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);
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;
non_serviced->push_back(PATH_SEPARATOR);
}
-
existing->insert(real);
}
// "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);
}
}
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.
}
// 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;
}
}
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);
}
}
}
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)
* -- 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)
{
}
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))
{
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)
{
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)
{
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)
{
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)
{
}
// 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;
}
}
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;
}
};
// 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;
}
// 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
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;
}
// 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;
}
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);
{
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;
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);
{
// 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);
}
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;
}
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);
}
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;
}
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:
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;
../deps_format.cpp
../deps_entry.cpp
../fx_definition.cpp
+ ../version.cpp
)
// 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()
{
{
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;
+}
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;
../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
#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)
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;
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))
--- /dev/null
+// 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;
+}
--- /dev/null
+// 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
}
}
+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_
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;
+}
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,
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
.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);
+ }
}
}
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)
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)
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)
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)
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)
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();
+ }
}
}
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;
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);
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>();
case DependencyContextStrings.AssetTypePropertyName:
runtimeTarget.Type = Pool(propertyValue);
break;
+ case DependencyContextStrings.AssemblyVersionPropertyName:
+ runtimeTarget.AssemblyVersion = propertyValue;
+ break;
+ case DependencyContextStrings.FileVersionPropertyName:
+ runtimeTarget.FileVersion = propertyValue;
+ break;
}
}
{
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) != "_._")));
}
}
}
public IEnumerable<Dependency> Dependencies;
- public List<string> Runtimes;
+ public List<RuntimeFile> Runtimes;
- public List<string> Natives;
+ public List<RuntimeFile> Natives;
public List<string> Compilations;
public string Path;
public string Rid;
+
+ public string AssemblyVersion;
+
+ public string FileVersion;
}
private struct LibraryStub
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
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))
);
}
{
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
{
}
}
- 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));
}
}
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 =
{
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>
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
--- /dev/null
+// 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; }
+ }
+}
// 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);
// 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")
.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:
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)
dir.Create();
string fxName = "UberFx";
+ string testPackage = "System.Collections.Immutable/1.0.0";
string testAssembly = "System.Collections.Immutable";
// Create the deps.json. Generated file:
},
"targets": {
"UberFx": {
- "System.Collections.Immutable": {
+ "System.Collections.Immutable/1.0.0": {
"runtime": {
"System.Collections.Immutable.dll": {}
}
}
},
"libraries": {
- "System.Collections.Immutable": {
+ "System.Collections.Immutable/1.0.0": {
"type": "assemblyreference",
"serviceable": false,
"sha512": ""
}
}
*/
+ 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(
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
)
)
)
),
new JProperty("libraries",
new JObject(
- new JProperty(testAssembly,
+ new JProperty(testPackage,
new JObject(
new JProperty("type", "assemblyreference"),
new JProperty("serviceable", false),
[Fact]
public void GroupsRuntimeAssets()
{
- var context = Read(@"
+ string json = @"
{
""targets"": {
"".NETStandard,Version=v1.5"": {
""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]
[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""
""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""}
""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");
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");
}
[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",
"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"),
library.Should().HavePropertyValue("path", "PackagePath");
library.Should().HavePropertyValue("hashPath", "PackageHashPath");
library.Should().HavePropertyValue("runtimeStoreManifestName", "placeHolderManifest.xml");
+
+ return runtimeAssembly;
}
[Fact]
}
[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",
"1.2.3",
"HASH",
new [] {
- new RuntimeAssetGroup(string.Empty, "Banana.dll")
+ group
},
new [] {
new RuntimeAssetGroup(string.Empty, "runtimes\\osx\\native\\native.dylib")
// 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]
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);
}
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);
}