require 'nokogiri' require 'albacore/logging' require 'albacore/semver' require 'albacore/package_repo' require 'albacore/paket' module Albacore # error raised from Project#output_path if the given configuration wasn't # found class ConfigurationNotFoundError < ::StandardError end # a project encapsulates the properties from a xxproj file. class Project include Logging attr_reader :proj_path_base, :proj_filename, :proj_xml_node def initialize proj_path raise ArgumentError, 'project path does not exist' unless File.exists? proj_path.to_s proj_path = proj_path.to_s unless proj_path.is_a? String @proj_xml_node = Nokogiri.XML(open(proj_path)) @proj_path_base, @proj_filename = File.split proj_path sanity_checks end # Get the project GUID without '{' or '}' characters. def guid guid_raw.gsub /[\{\}]/, '' end # Get the project GUID as it is in the project file. def guid_raw read_property 'ProjectGuid' end # Get the project id specified in the project file. Defaults to #name. def id (read_property 'Id') || name end # Get the project name specified in the project file. This is the same as # the title of the nuspec and, if Id is not specified, also the id of the # nuspec. def name (read_property 'Name') || asmname end # The same as #name alias_method :title, :name # get the assembly name specified in the project file def asmname read_property 'AssemblyName' end # Get the root namespace of the project def namespace read_property 'RootNamespace' end # gets the version from the project file def version read_property 'Version' end # gets any authors from the project file def authors read_property 'Authors' end def description read_property 'Description' end # the license that the project has defined in the metadata in the xxproj file. def license read_property 'License' end # gets the output path of the project given the configuration or raise # an error otherwise def output_path conf try_output_path conf || raise(ConfigurationNotFoundError, "could not find configuration '#{conf}'") end def try_output_path conf default_platform = @proj_xml_node.css('Project PropertyGroup Platform').first.inner_text || 'AnyCPU' path = @proj_xml_node.css("Project PropertyGroup[Condition*='#{conf}|#{default_platform}'] OutputPath") # path = @proj_xml_node.xpath("//Project/PropertyGroup[matches(@Condition, '#{conf}')]/OutputPath") debug { "#{name}: output path node[#{conf}]: #{ (path.empty? ? 'empty' : path.inspect) } [albacore: project]" } return path.inner_text unless path.empty? nil end # This is the output path if the project file doens't have a configured # 'Configuration' condition like all default project files have that come # from Visual Studio/Xamarin Studio. def fallback_output_path fallback = @proj_xml_node.css("Project PropertyGroup OutputPath").first condition = fallback.parent['Condition'] || 'No \'Condition\' specified' warn "chose an OutputPath in: '#{self}' for Configuration: <#{condition}> [albacore: project]" fallback.inner_text end # Gets the relative location (to the project base path) of the dll # that it will output def output_dll conf Paths.join(output_path(conf) || fallback_output_path, "#{asmname}.dll") end # find the NodeList reference list def find_refs # should always be there @proj_xml_node.css("Project Reference") end def faulty_refs find_refs.to_a.keep_if{ |r| r.children.css("HintPath").empty? } end def has_faulty_refs? faulty_refs.any? end def has_packages_config? File.exists? package_config end def has_paket_deps? File.exists? paket_deps end def has_paket_refs? File.exists? paket_refs end def declared_packages return nuget_packages || paket_packages || [] end def declared_projects @proj_xml_node.css("ProjectReference").collect do |proj_ref| debug do ref_name = proj_ref.css("Name").inner_text "found project reference: #{name} => #{ref_name} [albacore: project]" end Project.new(File.join(@proj_path_base, Albacore::Paths.normalise_slashes(proj_ref['Include']))) end end # returns a list of the files included in the project def included_files ['Compile','Content','EmbeddedResource','None'].map { |item_name| proj_xml_node.xpath("/x:Project/x:ItemGroup/x:#{item_name}", 'x' => "http://schemas.microsoft.com/developer/msbuild/2003").collect { |f| debug "#{name}: #included_files looking at '#{f}' [albacore: project]" link = f.elements.select{ |el| el.name == 'Link' }.map { |el| el.content }.first OpenStruct.new( :item_name => item_name.downcase, :link => link, :include => f['Include'] ) } }.flatten end # Find all packages that have been declared and can be found in ./src/packages. # This is mostly useful if you have that repository structure. # returns enumerable Package def find_packages declared_packages.collect do |package| guess = ::Albacore::PackageRepo.new(%w|./packages ./src/packages|).find_latest package.id debug "#{name}: guess: #{guess} [albacore: project]" guess end end # get the path of the project file def path File.join @proj_path_base, @proj_filename end # save the xml def save(output = nil) output = path unless output File.open(output, 'w') { |f| @proj_xml_node.write_xml_to f } end # get the full path of 'packages.config' def package_config File.join @proj_path_base, 'packages.config' end # Get the full path of 'paket.dependencies' def paket_deps File.join @proj_path_base, 'paket.dependencies' end # Get the full path of 'paket.references' def paket_refs File.join @proj_path_base, 'paket.references' end # Gets the path of the project file def to_s path end private def nuget_packages return nil unless has_packages_config? doc = Nokogiri.XML(open(package_config)) doc.xpath("//packages/package").collect { |p| OpenStruct.new(:id => p[:id], :version => p[:version], :target_framework => p[:targetFramework], :semver => Albacore::SemVer.parse(p[:version], '%M.%m.%p', false) ) } end def all_paket_deps return @all_paket_deps if @all_paket_deps arr = File.open('paket.lock', 'r') do |io| Albacore::Paket.parse_paket_lock(io.readlines.map(&:chomp)) end @all_paket_deps = Hash[arr] end def paket_packages return nil unless has_paket_deps? || has_paket_refs? info { "extracting paket dependencies from '#{to_s}' and 'paket.{dependencies,references}' in its folder [project: paket_package]" } all_refs = [] if has_paket_refs? File.open paket_refs, 'r' do |io| io.readlines.map(&:chomp).compact.each do |line| paket_package_by_id! line, all_refs, 'referenced' end end end if has_paket_deps? File.open paket_deps, 'r' do |io| io.readlines.map(&:chomp).compact.each do |line| paket_package_by_id! line, all_refs, 'dependent' end end end all_refs end def paket_package_by_id! id, arr, ref_type pkg = all_paket_deps[id] if pkg debug { "found #{ref_type} package '#{id}' [project: paket_packages]" } arr << pkg else warn { "found #{ref_type} package '#{id}' not in paket.lock [project: paket_packages]" } end end def sanity_checks warn { "project '#{@proj_filename}' has no name" } unless name end def read_property prop_name txt = @proj_xml_node.css("Project PropertyGroup #{prop_name}").inner_text txt.length == 0 ? nil : txt.strip end # find the node of pkg_id def self.find_ref proj_xml, pkg_id @proj_xml.css("Project ItemGroup Reference[@Include*='#{pkg_id},']").first end end end